[IMP] resource: add resource mixin adding fields and an API for resource

Purpose is to replace existing inherits of resource.resource by an inherit
on the mixin itself. The mixin offers several methods to compute working
intervals, attendance, hours, ... This mixin will be improved in future
commits when new methods that will be required with future improvements
of various service apps.

This commit also adds tests for the mixin itself. A new model is defined
only for testing purpose to test the mixin.
This commit is contained in:
Thibault Delavallée
2017-02-27 14:04:48 +01:00
parent 27d41ccbb5
commit 9944209ef7
7 changed files with 197 additions and 0 deletions
+1
View File
@@ -2,3 +2,4 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import models
import tests.test_resource_model
+2
View File
@@ -2,4 +2,6 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import resource
import resource_mixin
import res_company
import res_users
+14
View File
@@ -0,0 +1,14 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models
class ResUsers(models.Model):
_inherit = 'res.users'
resource_ids = fields.One2many(
'resource.resource', 'user_id', 'Resources')
resource_calendar_id = fields.Many2one(
'resource.calendar', 'Default Working Hours',
related='resource_ids.calendar_id')
+91
View File
@@ -0,0 +1,91 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import timedelta
from odoo import api, fields, models
class ResourceMixin(models.AbstractModel):
_name = "resource.mixin"
_description = 'Resource Mixin'
resource_id = fields.Many2one(
'resource.resource', 'Resource',
auto_join=True, index=True, ondelete='restrict', required=True)
company_id = fields.Many2one(
'res.company', 'Company',
default=lambda self: self.env['res.company']._company_default_get(),
index=True, related='resource_id.company_id', store=True)
resource_calendar_id = fields.Many2one(
'resource.calendar', 'Working Hours',
default=lambda self: self.env['res.company']._company_default_get().resource_calendar_id,
index=True, related='resource_id.calendar_id')
@api.model
def create(self, values):
if not values.get('resource_id'):
resource = self.env['resource.resource'].create({
'name': values.get(self._rec_name)
})
values['resource_id'] = resource.id
return super(ResourceMixin, self).create(values)
def get_work_days_count(self, from_datetime, to_datetime, calendar=None):
""" Return the number of work days for the resource, taking into account
leaves. An optional calendar can be given in case multiple calendars can
be used on the resource. """
return self.get_work_days_data(from_datetime, to_datetime, calendar=calendar)['days']
def get_work_days_data(self, from_datetime, to_datetime, calendar=None):
days_count = 0.0
total_work_time = timedelta()
calendar = calendar or self.resource_calendar_id
for day_intervals in calendar._iter_work_intervals(
from_datetime, to_datetime, self.resource_id.id,
compute_leaves=True):
theoric_hours = self.get_day_work_hours_count(day_intervals[0][0].date(), calendar=calendar)
work_time = sum((interval[1] - interval[0] for interval in day_intervals), timedelta())
total_work_time += work_time
days_count += round((work_time.total_seconds() / 3600 / theoric_hours) * 4) / 4
return {
'days': days_count,
'hours': total_work_time.total_seconds() / 3600,
}
def iter_works(self, from_datetime, to_datetime, calendar=None):
calendar = calendar or self.resource_calendar_id
return calendar._iter_work_intervals(from_datetime, to_datetime, self.resource_id.id)
def get_leaves_day_count(self, from_datetime, to_datetime, calendar=None):
""" Return the number of leave days for the resource, taking into account
attendances. An optional calendar can be given in case multiple calendars
can be used on the resource. """
days_count = 0.0
calendar = calendar or self.resource_calendar_id
for day_intervals in calendar._iter_leave_intervals(from_datetime, to_datetime, self.resource_id.id):
theoric_hours = self.get_day_work_hours_count(day_intervals[0][0].date(), calendar=calendar)
leave_time = sum((interval[1] - interval[0] for interval in day_intervals), timedelta())
days_count += round((leave_time.total_seconds()/3600 / theoric_hours) * 4) / 4
return days_count
def iter_leaves(self, from_datetime, to_datetime, calendar=None):
calendar = calendar or self.resource_calendar_id
return calendar._iter_leave_intervals(from_datetime, to_datetime, self.resource_id.id)
def get_start_work_hour(self, day_dt, calendar=None):
calendar = calendar or self.resource_calendar_id
work_intervals = calendar._get_day_work_intervals(day_dt, resource_id=self.resource_id.id)
return work_intervals and work_intervals[0][0]
def get_end_work_hour(self, day_dt, calendar=None):
calendar = calendar or self.resource_calendar_id
work_intervals = calendar._get_day_work_intervals(day_dt, resource_id=self.resource_id.id)
return work_intervals and work_intervals[-1][1]
def get_day_work_hours_count(self, day_date, calendar=None):
calendar = calendar or self.resource_calendar_id
attendances = calendar._get_day_attendances(day_date, False, False)
if not attendances:
return 0
return sum(map(lambda i: float(i.hour_to) - float(i.hour_from), attendances))
@@ -7,3 +7,4 @@ access_resource_resource,resource.resource,model_resource_resource,base.group_sy
access_resource_resource_all,resource.resource all,model_resource_resource,base.group_user,1,0,0,0
access_resource_calendar_leaves_user,resource.calendar.leaves,model_resource_calendar_leaves,base.group_user,1,1,1,1
access_resource_calendar_leaves,resource.calendar.leaves,model_resource_calendar_leaves,base.group_system,1,1,1,1
access_resource_test_all,resource.test.all,model_resource_test,base.group_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
7 access_resource_resource_all resource.resource all model_resource_resource base.group_user 1 0 0 0
8 access_resource_calendar_leaves_user resource.calendar.leaves model_resource_calendar_leaves base.group_user 1 1 1 1
9 access_resource_calendar_leaves resource.calendar.leaves model_resource_calendar_leaves base.group_system 1 1 1 1
10 access_resource_test_all resource.test.all model_resource_test base.group_user 1 1 1 1
+76
View File
@@ -458,3 +458,79 @@ class TestWorkDays(TestResourceCommon):
self.assertFalse(r.calendar_id._is_work_day(date(1932, 11, 19), r.id)) # sat
self.assertFalse(r.calendar_id._is_work_day(date(1932, 11, 20), r.id)) # sun
self.assertFalse(r.calendar_id._is_work_day(date(1932, 12, 6), r.id)) # personal leave
class TestResourceMixin(TestResourceCommon):
def setUp(self):
super(TestResourceMixin, self).setUp()
self.lost_user = self.env['res.users'].with_context(
no_reset_password=True,
mail_create_nosubscribe=True
).create({
'name': 'Désiré Boideladodo',
'login': 'desire',
'tz': 'Indian/Reunion',
'groups_id': [(6, 0, [self.env.ref('base.group_user').id])]
})
self.test = self.env['resource.test'].with_context(default_resource_calendar_id=self.calendar.id).create({'name': 'Test'})
def test_basics(self):
self.assertEqual(self.env['resource.test'].create({'name': 'Test'}).resource_calendar_id, self.env.user.company_id.resource_calendar_id)
self.assertEqual(self.test.resource_calendar_id, self.calendar)
def test_work_days_count(self):
# user in timezone UTC-9 asks for work hours
self.env.user.tz = 'US/Alaska'
res = self.test.get_work_days_count(
to_naive_utc(Datetime.from_string('2013-02-12 06:00:00'), self.env.user),
to_naive_utc(Datetime.from_string('2013-02-22 23:00:00'), self.env.user))
self.assertEqual(res, 3.75) # generic leaves, 3 hours
res = self.test.get_work_days_count(
to_naive_utc(Datetime.from_string('2013-02-12 06:00:00'), self.env.user),
to_naive_utc(Datetime.from_string('2013-02-22 20:00:00'), self.env.user))
self.assertEqual(res, 3.5) # last day is truncated of 3 hours on 12)
self.env['resource.calendar.leaves'].create({
'name': 'Timezoned Leaves',
'calendar_id': self.test.resource_calendar_id.id,
'resource_id': self.test.resource_id.id,
'date_from': to_naive_utc(Datetime.from_string('2013-02-13 10:00:00'), self.env.user),
'date_to': to_naive_utc(Datetime.from_string('2013-02-17 12:00:00'), self.env.user)
})
res = self.test.get_work_days_count(
to_naive_utc(Datetime.from_string('2013-02-12 06:00:00'), self.env.user),
to_naive_utc(Datetime.from_string('2013-02-22 20:00:00'), self.env.user))
self.assertEqual(res, 2.5) # one day is on leave and last day is truncated of 3 hours on 12)
def test_work_days_count_timezones_ultra(self):
# user in timezone UTC+4 is attached to the resource and create leaves
self.test.resource_id.write({
'user_id': self.lost_user.id,
})
reunion_leave = self.env['resource.calendar.leaves'].sudo(self.lost_user).create({
'name': 'Timezoned Leaves',
'calendar_id': self.test.resource_calendar_id.id,
'resource_id': self.test.resource_id.id,
'date_from': to_naive_utc(Datetime.from_string('2013-02-12 10:00:00'), self.lost_user),
'date_to': to_naive_utc(Datetime.from_string('2013-02-12 12:00:00'), self.lost_user)
})
self.assertEqual(reunion_leave.tz, 'Indian/Reunion')
# user in timezone UTC-9 read and manipulate leaves
self.env.user.tz = 'US/Alaska'
res = self.test.get_work_days_data(
to_naive_utc(Datetime.from_string('2013-02-12 06:00:00'), self.env.user),
to_naive_utc(Datetime.from_string('2013-02-12 20:00:00'), self.env.user))
self.assertEqual(res['days'], 0.75)
self.assertEqual(res['hours'], 6.0)
# user in timezone UTC+4 read and manipulate leaves
res = self.test.sudo(self.lost_user).get_work_days_data(
to_naive_utc(Datetime.from_string('2013-02-12 06:00:00'), self.env.user),
to_naive_utc(Datetime.from_string('2013-02-12 20:00:00'), self.env.user))
self.assertEqual(res['days'], 0.75)
self.assertEqual(res['hours'], 6.0)
@@ -0,0 +1,12 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models
class MailTest(models.Model):
_description = 'Test Resource Model'
_name = 'resource.test'
_inherit = ['resource.mixin']
name = fields.Char()