diff options
| author | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
|---|---|---|
| committer | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
| commit | 3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch) | |
| tree | a44932296ef4a9b71d5f010906253d8c53727726 /addons/resource/models/resource_mixin.py | |
| parent | 0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff) | |
initial commit 2
Diffstat (limited to 'addons/resource/models/resource_mixin.py')
| -rw-r--r-- | addons/resource/models/resource_mixin.py | 219 |
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 |
