summaryrefslogtreecommitdiff
path: root/addons/resource/models/resource_mixin.py
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/resource/models/resource_mixin.py
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/resource/models/resource_mixin.py')
-rw-r--r--addons/resource/models/resource_mixin.py219
1 files changed, 219 insertions, 0 deletions
diff --git a/addons/resource/models/resource_mixin.py b/addons/resource/models/resource_mixin.py
new file mode 100644
index 00000000..36a758e5
--- /dev/null
+++ b/addons/resource/models/resource_mixin.py
@@ -0,0 +1,219 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from collections import defaultdict
+from dateutil.relativedelta import relativedelta
+from pytz import utc
+
+from odoo import api, fields, models
+
+
+def timezone_datetime(time):
+ if not time.tzinfo:
+ time = time.replace(tzinfo=utc)
+ return time
+
+
+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.company,
+ index=True, related='resource_id.company_id', store=True, readonly=False)
+ resource_calendar_id = fields.Many2one(
+ 'resource.calendar', 'Working Hours',
+ default=lambda self: self.env.company.resource_calendar_id,
+ index=True, related='resource_id.calendar_id', store=True, readonly=False)
+ tz = fields.Selection(
+ string='Timezone', related='resource_id.tz', readonly=False,
+ help="This field is used in order to define in which timezone the resources will work.")
+
+ @api.model
+ def create(self, values):
+ if not values.get('resource_id'):
+ resource_vals = {'name': values.get(self._rec_name)}
+ tz = (values.pop('tz', False) or
+ self.env['resource.calendar'].browse(values.get('resource_calendar_id')).tz)
+ if tz:
+ resource_vals['tz'] = tz
+ resource = self.env['resource.resource'].create(resource_vals)
+ values['resource_id'] = resource.id
+ return super(ResourceMixin, self).create(values)
+
+ def copy_data(self, default=None):
+ if default is None:
+ default = {}
+ resource = self.resource_id.copy()
+ default['resource_id'] = resource.id
+ default['company_id'] = resource.company_id.id
+ default['resource_calendar_id'] = resource.calendar_id.id
+ return super(ResourceMixin, self).copy_data(default)
+
+ # YTI TODO: Remove me in master
+ def _get_work_days_data(self, from_datetime, to_datetime, compute_leaves=True, calendar=None, domain=None):
+ self.ensure_one()
+ return self._get_work_days_data_batch(
+ from_datetime,
+ to_datetime,
+ compute_leaves=compute_leaves,
+ calendar=calendar,
+ domain=domain
+ )[self.id]
+
+ def _get_work_days_data_batch(self, from_datetime, to_datetime, compute_leaves=True, calendar=None, domain=None):
+ """
+ By default the resource calendar is used, but it can be
+ changed using the `calendar` argument.
+
+ `domain` is used in order to recognise the leaves to take,
+ None means default value ('time_type', '=', 'leave')
+
+ Returns a dict {'days': n, 'hours': h} containing the
+ quantity of working time expressed as days and as hours.
+ """
+ resources = self.mapped('resource_id')
+ mapped_employees = {e.resource_id.id: e.id for e in self}
+ result = {}
+
+ # naive datetimes are made explicit in UTC
+ from_datetime = timezone_datetime(from_datetime)
+ to_datetime = timezone_datetime(to_datetime)
+
+ mapped_resources = defaultdict(lambda: self.env['resource.resource'])
+ for record in self:
+ mapped_resources[calendar or record.resource_calendar_id] |= record.resource_id
+
+ for calendar, calendar_resources in mapped_resources.items():
+ if not calendar:
+ for calendar_resource in calendar_resources:
+ result[calendar_resource.id] = {'days': 0, 'hours': 0}
+ continue
+ day_total = calendar._get_resources_day_total(from_datetime, to_datetime, calendar_resources)
+
+ # actual hours per day
+ if compute_leaves:
+ intervals = calendar._work_intervals_batch(from_datetime, to_datetime, calendar_resources, domain)
+ else:
+ intervals = calendar._attendance_intervals_batch(from_datetime, to_datetime, calendar_resources)
+
+ for calendar_resource in calendar_resources:
+ result[calendar_resource.id] = calendar._get_days_data(intervals[calendar_resource.id], day_total[calendar_resource.id])
+
+ # convert "resource: result" into "employee: result"
+ return {mapped_employees[r.id]: result[r.id] for r in resources}
+
+ # YTI TODO: Remove me in master
+ def _get_leave_days_data(self, from_datetime, to_datetime, calendar=None, domain=None):
+ self.ensure_one()
+ return self._get_leave_days_data_batch(
+ from_datetime,
+ to_datetime,
+ calendar=calendar,
+ domain=domain
+ )[self.id]
+
+ def _get_leave_days_data_batch(self, from_datetime, to_datetime, calendar=None, domain=None):
+ """
+ By default the resource calendar is used, but it can be
+ changed using the `calendar` argument.
+
+ `domain` is used in order to recognise the leaves to take,
+ None means default value ('time_type', '=', 'leave')
+
+ Returns a dict {'days': n, 'hours': h} containing the number of leaves
+ expressed as days and as hours.
+ """
+ resources = self.mapped('resource_id')
+ mapped_employees = {e.resource_id.id: e.id for e in self}
+ result = {}
+
+ # naive datetimes are made explicit in UTC
+ from_datetime = timezone_datetime(from_datetime)
+ to_datetime = timezone_datetime(to_datetime)
+
+ mapped_resources = defaultdict(lambda: self.env['resource.resource'])
+ for record in self:
+ mapped_resources[calendar or record.resource_calendar_id] |= record.resource_id
+
+ for calendar, calendar_resources in mapped_resources.items():
+ day_total = calendar._get_resources_day_total(from_datetime, to_datetime, calendar_resources)
+
+ # compute actual hours per day
+ attendances = calendar._attendance_intervals_batch(from_datetime, to_datetime, calendar_resources)
+ leaves = calendar._leave_intervals_batch(from_datetime, to_datetime, calendar_resources, domain)
+
+ for calendar_resource in calendar_resources:
+ result[calendar_resource.id] = calendar._get_days_data(
+ attendances[calendar_resource.id] & leaves[calendar_resource.id],
+ day_total[calendar_resource.id]
+ )
+
+ # convert "resource: result" into "employee: result"
+ return {mapped_employees[r.id]: result[r.id] for r in resources}
+
+ def _adjust_to_calendar(self, start, end):
+ resource_results = self.resource_id._adjust_to_calendar(start, end)
+ # change dict keys from resources to associated records.
+ return {
+ record: resource_results[record.resource_id]
+ for record in self
+ }
+
+ def list_work_time_per_day(self, from_datetime, to_datetime, calendar=None, domain=None):
+ """
+ By default the resource calendar is used, but it can be
+ changed using the `calendar` argument.
+
+ `domain` is used in order to recognise the leaves to take,
+ None means default value ('time_type', '=', 'leave')
+
+ Returns a list of tuples (day, hours) for each day
+ containing at least an attendance.
+ """
+ resource = self.resource_id
+ calendar = calendar or self.resource_calendar_id
+
+ # naive datetimes are made explicit in UTC
+ if not from_datetime.tzinfo:
+ from_datetime = from_datetime.replace(tzinfo=utc)
+ if not to_datetime.tzinfo:
+ to_datetime = to_datetime.replace(tzinfo=utc)
+
+ intervals = calendar._work_intervals_batch(from_datetime, to_datetime, resource, domain)[resource.id]
+ result = defaultdict(float)
+ for start, stop, meta in intervals:
+ result[start.date()] += (stop - start).total_seconds() / 3600
+ return sorted(result.items())
+
+ def list_leaves(self, from_datetime, to_datetime, calendar=None, domain=None):
+ """
+ By default the resource calendar is used, but it can be
+ changed using the `calendar` argument.
+
+ `domain` is used in order to recognise the leaves to take,
+ None means default value ('time_type', '=', 'leave')
+
+ Returns a list of tuples (day, hours, resource.calendar.leaves)
+ for each leave in the calendar.
+ """
+ resource = self.resource_id
+ calendar = calendar or self.resource_calendar_id
+
+ # naive datetimes are made explicit in UTC
+ if not from_datetime.tzinfo:
+ from_datetime = from_datetime.replace(tzinfo=utc)
+ if not to_datetime.tzinfo:
+ to_datetime = to_datetime.replace(tzinfo=utc)
+
+ attendances = calendar._attendance_intervals_batch(from_datetime, to_datetime, resource)[resource.id]
+ leaves = calendar._leave_intervals_batch(from_datetime, to_datetime, resource, domain)[resource.id]
+ result = []
+ for start, stop, leave in (leaves & attendances):
+ hours = (stop - start).total_seconds() / 3600
+ result.append((start.date(), hours, leave))
+ return result