diff --git a/addons/resource/models/resource.py b/addons/resource/models/resource.py index c5c010374c5..6bba1a1911a 100644 --- a/addons/resource/models/resource.py +++ b/addons/resource/models/resource.py @@ -291,7 +291,7 @@ class ResourceCalendar(models.Model): return day_date + relativedelta(days=days) @api.multi - def _get_leave_intervals(self, resource_id=None, start_datetime=None, end_datetime=None): + def _get_leave_intervals(self, resource_id=None, start_datetime=None, end_datetime=None, domain=None): """Get the leaves of the calendar. Leaves can be filtered on the resource, and on a start and end datetime. @@ -303,16 +303,18 @@ class ResourceCalendar(models.Model): :return list leaves: list of time intervals """ self.ensure_one() if resource_id: - domain = ['|', ('resource_id', '=', resource_id), ('resource_id', '=', False)] + search_domain = ['|', ('resource_id', '=', resource_id), ('resource_id', '=', False)] else: - domain = [('resource_id', '=', False)] + search_domain = [('resource_id', '=', False)] if start_datetime: # domain += [('date_to', '>', fields.Datetime.to_string(to_naive_utc(start_datetime, self.env.user)))] - domain += [('date_to', '>', fields.Datetime.to_string(start_datetime + timedelta(days=-1)))] + search_domain += [('date_to', '>', fields.Datetime.to_string(start_datetime + timedelta(days=-1)))] if end_datetime: # domain += [('date_from', '<', fields.Datetime.to_string(to_naive_utc(end_datetime, self.env.user)))] - domain += [('date_from', '<', fields.Datetime.to_string(end_datetime + timedelta(days=1)))] - leaves = self.env['resource.calendar.leaves'].search(domain + [('calendar_id', '=', self.id)]) + search_domain += [('date_from', '<', fields.Datetime.to_string(end_datetime + timedelta(days=1)))] + if domain: + search_domain += domain + leaves = self.env['resource.calendar.leaves'].search(search_domain + [('calendar_id', '=', self.id)]) filtered_leaves = self.env['resource.calendar.leaves'] for leave in leaves: @@ -343,7 +345,7 @@ class ResourceCalendar(models.Model): yield self._interval_new(dt_f, dt_t, {'attendances': calendar_working_day}) @api.multi - def _get_day_work_intervals(self, day_date, start_time=None, end_time=None, compute_leaves=False, resource_id=None): + def _get_day_work_intervals(self, day_date, start_time=None, end_time=None, compute_leaves=False, resource_id=None, domain=None): """ Get the working intervals of the day given by day_date based on current calendar. Input should be given in current user timezone and output is given in naive UTC, ready to be used by the orm or webclient. @@ -371,7 +373,8 @@ class ResourceCalendar(models.Model): leaves = self._get_leave_intervals( resource_id=resource_id, start_datetime=datetime.datetime.combine(day_date, start_time), - end_datetime=datetime.datetime.combine(day_date, end_time)) + end_datetime=datetime.datetime.combine(day_date, end_time), + domain=domain) working_intervals = [ sub_interval for interval in working_intervals @@ -383,7 +386,7 @@ class ResourceCalendar(models.Model): to_naive_utc(interval[1], self.env.user), interval[2]) for interval in working_intervals] - def _get_day_leave_intervals(self, day_date, start_time, end_time, resource_id): + def _get_day_leave_intervals(self, day_date, start_time, end_time, resource_id, domain=None): """ Get the leave intervals of the day given by day_date based on current calendar. Input should be given in current user timezone and output is given in naive UTC, ready to be used by the orm or webclient. @@ -406,7 +409,8 @@ class ResourceCalendar(models.Model): leaves_intervals = self._get_leave_intervals( resource_id=resource_id, start_datetime=datetime.datetime.combine(day_date, start_time), - end_datetime=datetime.datetime.combine(day_date, end_time)) + end_datetime=datetime.datetime.combine(day_date, end_time), + domain=domain) final_intervals = [i for i in [self._interval_and(leave_interval, work_interval) @@ -423,7 +427,7 @@ class ResourceCalendar(models.Model): # Main computation API # -------------------------------------------------- - def _iter_work_intervals(self, start_dt, end_dt, resource_id, compute_leaves=True): + def _iter_work_intervals(self, start_dt, end_dt, resource_id, compute_leaves=True, domain=None): """ Lists the current resource's work intervals between the two provided datetimes (inclusive) expressed in UTC, for each worked day. """ if not end_dt: @@ -448,11 +452,12 @@ class ResourceCalendar(models.Model): start_time=start_time, end_time=end_time, compute_leaves=compute_leaves, - resource_id=resource_id) + resource_id=resource_id, + domain=domain) if intervals: yield intervals - def _iter_leave_intervals(self, start_dt, end_dt, resource_id): + def _iter_leave_intervals(self, start_dt, end_dt, resource_id, domain=None): """ Lists the current resource's leave intervals between the two provided datetimes (inclusive) expressed in UTC. """ if not end_dt: @@ -476,22 +481,23 @@ class ResourceCalendar(models.Model): day.date(), start_time, end_time, - resource_id) + resource_id, + domain=domain) if intervals: yield intervals - def _iter_work_hours_count(self, from_datetime, to_datetime, resource_id): + def _iter_work_hours_count(self, from_datetime, to_datetime, resource_id, domain=None): """ Lists the current resource's work hours count between the two provided datetime expressed in naive UTC. """ - for interval in self._iter_work_intervals(from_datetime, to_datetime, resource_id): + for interval in self._iter_work_intervals(from_datetime, to_datetime, resource_id, domain=domain): td = timedelta() for work_interval in interval: td += work_interval[1] - work_interval[0] yield (interval[0][0].date(), td.total_seconds() / 3600.0) - def _iter_work_days(self, from_date, to_date, resource_id): + def _iter_work_days(self, from_date, to_date, resource_id, domain=None): """ Lists the current resource's work days between the two provided dates (inclusive) expressed in naive UTC. @@ -507,7 +513,7 @@ class ResourceCalendar(models.Model): for interval in self._iter_work_intervals( datetime.datetime(from_date.year, from_date.month, from_date.day), datetime.datetime(to_date.year, to_date.month, to_date.day), - resource_id): + resource_id, domain=domain): yield interval[0][0].date() @api.multi @@ -519,11 +525,11 @@ class ResourceCalendar(models.Model): return bool(next(self._iter_work_days(date, date, resource_id), False)) @api.multi - def get_work_hours_count(self, start_dt, end_dt, resource_id, compute_leaves=True): + def get_work_hours_count(self, start_dt, end_dt, resource_id, compute_leaves=True, domain=None): """ Count number of work hours between two datetimes. For compute_leaves, resource_id: see _get_day_work_intervals. """ res = timedelta() - for intervals in self._iter_work_intervals(start_dt, end_dt, resource_id, compute_leaves=compute_leaves): + for intervals in self._iter_work_intervals(start_dt, end_dt, resource_id, compute_leaves=compute_leaves, domain=domain): for interval in intervals: res += interval[1] - interval[0] return res.total_seconds() / 3600.0 @@ -533,7 +539,7 @@ class ResourceCalendar(models.Model): # -------------------------------------------------- @api.multi - def _schedule_hours(self, hours, day_dt, compute_leaves=False, resource_id=None): + def _schedule_hours(self, hours, day_dt, compute_leaves=False, resource_id=None, domain=None): """ Schedule hours of work, using a calendar and an optional resource to compute working and leave days. This method can be used backwards, i.e. scheduling days before a deadline. For compute_leaves, resource_id: @@ -555,7 +561,7 @@ class ResourceCalendar(models.Model): day_dt_tz = to_naive_user_tz(day_dt, self.env.user) current_datetime = day_dt_tz - call_args = dict(compute_leaves=compute_leaves, resource_id=resource_id) + call_args = dict(compute_leaves=compute_leaves, resource_id=resource_id, domain=domain) while float_compare(remaining_hours, 0.0, precision_digits=2) in (1, 0) and iterations < 1000: if backwards: @@ -595,7 +601,7 @@ class ResourceCalendar(models.Model): return False @api.multi - def _schedule_days(self, days, day_dt, compute_leaves=False, resource_id=None): + def _schedule_days(self, days, day_dt, compute_leaves=False, resource_id=None, domain=None): """Schedule days of work, using a calendar and an optional resource to compute working and leave days. This method can be used backwards, i.e. scheduling days before a deadline. For compute_leaves, resource_id: @@ -619,7 +625,8 @@ class ResourceCalendar(models.Model): while planned_days < abs(days) and iterations < 100: working_intervals = self._get_day_work_intervals( current_datetime.date(), - compute_leaves=compute_leaves, resource_id=resource_id) + compute_leaves=compute_leaves, resource_id=resource_id, + domain=domain) if not self or working_intervals: # no calendar -> no working hours, but day is considered as worked planned_days += 1 intervals += working_intervals diff --git a/addons/resource/models/resource_mixin.py b/addons/resource/models/resource_mixin.py index 416bd2875e7..20a641ede26 100644 --- a/addons/resource/models/resource_mixin.py +++ b/addons/resource/models/resource_mixin.py @@ -41,19 +41,25 @@ class ResourceMixin(models.AbstractModel): default['resource_calendar_id'] = resource.calendar_id.id return super(ResourceMixin, self).copy_data(default) - def get_work_days_count(self, from_datetime, to_datetime, calendar=None): + def get_work_days_count(self, from_datetime, to_datetime, calendar=None, domain=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'] + return self.get_work_days_data(from_datetime, to_datetime, calendar=calendar, domain=domain)['days'] - def get_work_days_data(self, from_datetime, to_datetime, calendar=None): + def get_work_hours_count(self, from_datetime, to_datetime, calendar=None, domain=None): + """ Return the number of work hours 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, domain=domain)['hours'] + + def get_work_days_data(self, from_datetime, to_datetime, calendar=None, domain=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): + compute_leaves=True, domain=domain): 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 @@ -63,38 +69,52 @@ class ResourceMixin(models.AbstractModel): 'hours': total_work_time.total_seconds() / 3600, } - def iter_works(self, from_datetime, to_datetime, calendar=None): + def iter_works(self, from_datetime, to_datetime, calendar=None, domain=None): calendar = calendar or self.resource_calendar_id - return calendar._iter_work_intervals(from_datetime, to_datetime, self.resource_id.id) + return calendar._iter_work_intervals(from_datetime, to_datetime, self.resource_id.id, domain=domain) - def iter_work_hours_count(self, from_datetime, to_datetime, calendar=None): + def iter_work_hours_count(self, from_datetime, to_datetime, calendar=None, domain=None): calendar = calendar or self.resource_calendar_id - return calendar._iter_work_hours_count(from_datetime, to_datetime, self.resource_id.id) + return calendar._iter_work_hours_count(from_datetime, to_datetime, self.resource_id.id, domain=domain) - def get_leaves_day_count(self, from_datetime, to_datetime, calendar=None): + def get_leaves_day_count(self, from_datetime, to_datetime, calendar=None, domain=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. """ + return self.get_leaves_days_data(from_datetime, to_datetime, calendar=calendar, domain=domain)['days'] + + def get_leaves_hours_count(self, from_datetime, to_datetime, calendar=None, domain=None): + """ Return the number of leave hours for the resource, taking into account + attendances. An optional calendar can be given in case multiple calendars can + be used on the resource. """ + return self.get_leaves_days_data(from_datetime, to_datetime, calendar=calendar, domain=domain)['hours'] + + def get_leaves_days_data(self, from_datetime, to_datetime, calendar=None, domain=None): days_count = 0.0 + total_leave_time = timedelta() calendar = calendar or self.resource_calendar_id - for day_intervals in calendar._iter_leave_intervals(from_datetime, to_datetime, self.resource_id.id): + for day_intervals in calendar._iter_leave_intervals(from_datetime, to_datetime, self.resource_id.id, domain=domain): 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()) + total_leave_time += leave_time days_count += float_utils.round((leave_time.total_seconds() / 3600 / theoric_hours) * 4) / 4 - return days_count + return { + 'days': days_count, + 'hours': total_leave_time.total_seconds() / 3600, + } - def iter_leaves(self, from_datetime, to_datetime, calendar=None): + def iter_leaves(self, from_datetime, to_datetime, calendar=None, domain=None): calendar = calendar or self.resource_calendar_id - return calendar._iter_leave_intervals(from_datetime, to_datetime, self.resource_id.id) + return calendar._iter_leave_intervals(from_datetime, to_datetime, self.resource_id.id, domain=domain) - def get_start_work_hour(self, day_dt, calendar=None): + def get_start_work_hour(self, day_dt, calendar=None, domain=None): calendar = calendar or self.resource_calendar_id - work_intervals = calendar._get_day_work_intervals(day_dt, resource_id=self.resource_id.id) + work_intervals = calendar._get_day_work_intervals(day_dt, resource_id=self.resource_id.id, domain=domain) return work_intervals and work_intervals[0][0] - def get_end_work_hour(self, day_dt, calendar=None): + def get_end_work_hour(self, day_dt, calendar=None, domain=None): calendar = calendar or self.resource_calendar_id - work_intervals = calendar._get_day_work_intervals(day_dt, resource_id=self.resource_id.id) + work_intervals = calendar._get_day_work_intervals(day_dt, resource_id=self.resource_id.id, domain=domain) return work_intervals and work_intervals[-1][1] def get_day_work_hours_count(self, day_date, calendar=None): diff --git a/addons/resource/tests/test_resource.py b/addons/resource/tests/test_resource.py index 39861cf9805..ed1d81fd54f 100644 --- a/addons/resource/tests/test_resource.py +++ b/addons/resource/tests/test_resource.py @@ -698,6 +698,48 @@ class TestResourceMixin(TestResourceCommon): self.assertEqual(res['days'], 0.75) self.assertEqual(res['hours'], 6.0) + def test_days_count_with_domain(self): + self.test.resource_id.write({ + 'user_id': self.lost_user.id, + }) + + self.env['resource.calendar.leaves'].sudo(self.lost_user).create({ + 'name': 'first', + 'calendar_id': self.test.resource_calendar_id.id, + 'resource_id': self.test.resource_id.id, + 'date_from': to_naive_utc(Datetime.from_string('2018-02-05 08:00:00'), self.lost_user), + 'date_to': to_naive_utc(Datetime.from_string('2018-02-09 18:00:00'), self.lost_user) + }) + + self.env['resource.calendar.leaves'].sudo(self.lost_user).create({ + 'name': 'second', + 'calendar_id': self.test.resource_calendar_id.id, + 'resource_id': self.test.resource_id.id, + 'date_from': to_naive_utc(Datetime.from_string('2018-01-12 08:00:00'), self.lost_user), + 'date_to': to_naive_utc(Datetime.from_string('2018-01-13 18:00:00'), self.lost_user) + }) + + res = self.test.get_work_days_count( + to_naive_utc(Datetime.from_string('2018-02-04 06:00:00'), self.env.user), + to_naive_utc(Datetime.from_string('2018-02-14 20:00:00'), self.env.user), + domain=[('name', '=', '')] + ) + self.assertEqual(res, 3.0) + + res = self.test.get_work_days_count( + to_naive_utc(Datetime.from_string('2018-02-04 06:00:00'), self.env.user), + to_naive_utc(Datetime.from_string('2018-02-14 20:00:00'), self.env.user), + domain=[('name', '=', 'first')] + ) + self.assertEqual(res, 1.0) + + res = self.test.get_leaves_day_count( + to_naive_utc(Datetime.from_string('2018-02-04 06:00:00'), self.env.user), + to_naive_utc(Datetime.from_string('2018-02-14 20:00:00'), self.env.user), + domain=[('name', '=', 'first')] + ) + self.assertEqual(res, 2.0) + class TestGlobalLeaves(TestResourceCommon): def setUp(self):