diff --git a/addons/hr_work_entry/models/hr_work_entry.py b/addons/hr_work_entry/models/hr_work_entry.py index 4bdf029b268..ea65077538c 100644 --- a/addons/hr_work_entry/models/hr_work_entry.py +++ b/addons/hr_work_entry/models/hr_work_entry.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. +from collections import defaultdict from contextlib import contextmanager from dateutil.relativedelta import relativedelta import itertools @@ -77,19 +78,36 @@ class HrWorkEntry(models.Model): @api.depends('date_stop', 'date_start') def _compute_duration(self): + durations = self._get_duration_batch() for work_entry in self: - work_entry.duration = work_entry._get_duration(work_entry.date_start, work_entry.date_stop) + work_entry.duration = durations[work_entry.id] @api.depends('date_start', 'duration') def _compute_date_stop(self): for work_entry in self.filtered(lambda w: w.date_start and w.duration): work_entry.date_stop = work_entry.date_start + relativedelta(hours=work_entry.duration) + def _get_duration_batch(self): + result = {} + cached_periods = defaultdict(float) + for work_entry in self: + date_start = work_entry.date_start + date_stop = work_entry.date_stop + if not date_start or not date_stop: + result[work_entry.id] = 0.0 + continue + if (date_start, date_stop) in cached_periods: + result[work_entry.id] = cached_periods[(date_start, date_stop)] + else: + dt = date_stop - date_start + duration = dt.days * 24 + dt.seconds / 3600 # Number of hours + cached_periods[(date_start, date_stop)] = duration + result[work_entry.id] = duration + return result + + # YTI TODO: Remove me in master: Deprecated, use _get_duration_batch instead def _get_duration(self, date_start, date_stop): - if not date_start or not date_stop: - return 0 - dt = date_stop - date_start - return dt.days * 24 + dt.seconds / 3600 # Number of hours + return self._get_duration_batch()[self.id] def action_validate(self): """ diff --git a/addons/hr_work_entry_contract/models/hr_work_entry.py b/addons/hr_work_entry_contract/models/hr_work_entry.py index ac5a63cde6c..bb54d30e792 100644 --- a/addons/hr_work_entry_contract/models/hr_work_entry.py +++ b/addons/hr_work_entry_contract/models/hr_work_entry.py @@ -73,19 +73,42 @@ class HrWorkEntry(models.Model): continue super(HrWorkEntry, work_entry)._compute_date_stop() - def _get_duration(self, date_start, date_stop): - if not date_start or not date_stop: - return 0 - if self._get_duration_is_valid(): - calendar = self.contract_id.resource_calendar_id + def _is_duration_computed_from_calendar(self): + self.ensure_one() + return self._get_duration_is_valid() + + def _get_duration_batch(self): + super_work_entries = self.env['hr.work.entry'] + result = {} + # {(date_start, date_stop): {calendar: employees}} + mapped_periods = defaultdict(lambda: defaultdict(lambda: self.env['hr.employee'])) + for work_entry in self: + if not work_entry.date_start or not work_entry.date_stop or not work_entry._is_duration_computed_from_calendar(): + super_work_entries |= work_entry + continue + date_start = work_entry.date_start + date_stop = work_entry.date_stop + calendar = work_entry.contract_id.resource_calendar_id if not calendar: - return 0 - employee = self.contract_id.employee_id - contract_data = employee._get_work_days_data_batch( - date_start, date_stop, compute_leaves=False, calendar=calendar - )[employee.id] - return contract_data.get('hours', 0) - return super()._get_duration(date_start, date_stop) + result[work_entry.id] = 0.0 + continue + employee = work_entry.contract_id.employee_id + mapped_periods[(date_start, date_stop)][calendar] |= employee + + # {(date_start, date_stop): {calendar: {'hours': foo}}} + mapped_contract_data = defaultdict(lambda: defaultdict(lambda: {'hours': 0.0})) + for (date_start, date_stop), employees_by_calendar in mapped_periods.items(): + for calendar, employees in employees_by_calendar.items(): + mapped_contract_data[(date_start, date_stop)][calendar] = employees._get_work_days_data_batch( + date_start, date_stop, compute_leaves=False, calendar=calendar) + result = super(HrWorkEntry, super_work_entries)._get_duration_batch() + for work_entry in self - super_work_entries: + date_start = work_entry.date_start + date_stop = work_entry.date_stop + calendar = work_entry.contract_id.resource_calendar_id + employee = work_entry.contract_id.employee_id + result[work_entry.id] = mapped_contract_data[(date_start, date_stop)][calendar][employee.id]['hours'] + return result @api.model def _set_current_contract(self, vals): diff --git a/addons/hr_work_entry_holidays/models/hr_work_entry.py b/addons/hr_work_entry_holidays/models/hr_work_entry.py index 032ca1a187f..056eff67493 100644 --- a/addons/hr_work_entry_holidays/models/hr_work_entry.py +++ b/addons/hr_work_entry_holidays/models/hr_work_entry.py @@ -10,16 +10,8 @@ class HrWorkEntry(models.Model): leave_id = fields.Many2one('hr.leave', string='Time Off') leave_state = fields.Selection(related='leave_id.state') - def _get_duration(self, date_start, date_stop): - if not date_start or not date_stop: - return 0 - if not self.work_entry_type_id and self.leave_id: - calendar = self.contract_id.resource_calendar_id - employee = self.contract_id.employee_id - contract_data = employee._get_work_days_data_batch( - date_start, date_stop, compute_leaves=False, calendar=calendar)[employee.id] - return contract_data.get('hours', 0) - return super()._get_duration(date_start, date_stop) + def _is_duration_computed_from_calendar(self): + return super()._is_duration_computed_from_calendar() or bool(not self.work_entry_type_id and self.leave_id) def write(self, vals): if 'state' in vals and vals['state'] == 'cancelled':