From 06f36ddbe9d42cd91fb273eed293ffbd4acf1d66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Tue, 10 Jan 2017 18:34:30 +0100 Subject: [PATCH] [REF] resource: remove pseudo-default working hours computation Companies now always have a default calendar configured. All methods in resource module should therefore be able to use a real resource.calendar. This commit removes the computation based on a pseudo 8-17 calendar given as weird method parameters. Project is updated in order to use the company calendar by default. --- addons/project/models/project.py | 4 +- addons/project_issue/models/project_issue.py | 20 ++-- addons/resource/models/resource.py | 119 +++++++------------ addons/resource/tests/test_resource.py | 26 ---- 4 files changed, 60 insertions(+), 109 deletions(-) diff --git a/addons/project/models/project.py b/addons/project/models/project.py index a310bd6a5e2..4064e001f8f 100644 --- a/addons/project/models/project.py +++ b/addons/project/models/project.py @@ -185,7 +185,9 @@ class Project(models.Model): help="Whether this project should be displayed on the dashboard or not") label_tasks = fields.Char(string='Use Tasks as', default='Tasks', help="Gives label to tasks on project's kanban view.") tasks = fields.One2many('project.task', 'project_id', string="Task Activities") - resource_calendar_id = fields.Many2one('resource.calendar', string='Working Time', + resource_calendar_id = fields.Many2one( + 'resource.calendar', string='Working Time', + default=lambda self: self.env.user.company_id.resource_calendar_id.id, help="Timetable working hours to adjust the gantt diagram report") type_ids = fields.Many2many('project.task.type', 'project_task_type_rel', 'project_id', 'type_id', string='Tasks Stages') task_count = fields.Integer(compute='_compute_task_count', string="Tasks") diff --git a/addons/project_issue/models/project_issue.py b/addons/project_issue/models/project_issue.py index a141460110d..f5a77a745f4 100644 --- a/addons/project_issue/models/project_issue.py +++ b/addons/project_issue/models/project_issue.py @@ -84,21 +84,27 @@ class ProjectIssue(models.Model): @api.depends('create_date', 'date_closed', 'date_open') def _compute_day(self): for issue in self: - # if the working hours on the project are not defined, use default ones (8 -> 12 and 13 -> 17 * 5) - calendar = issue.project_id.resource_calendar_id - dt_create_date = fields.Datetime.from_string(issue.create_date) + if issue.date_open: dt_date_open = fields.Datetime.from_string(issue.date_open) issue.day_open = (dt_date_open - dt_create_date).total_seconds() / (24.0 * 3600) - issue.working_hours_open = calendar.get_working_hours(dt_create_date, dt_date_open, - compute_leaves=True, resource_id=False, default_interval=(8, 16)) + if issue.project_id.resource_calendar_id: + issue.working_hours_open = issue.project_id.resource_calendar_id.get_working_hours( + dt_create_date, dt_date_open, + compute_leaves=True, resource_id=False) + else: + issue.working_hours_open = 0 if issue.date_closed: dt_date_closed = fields.Datetime.from_string(issue.date_closed) issue.day_close = (dt_date_closed - dt_create_date).total_seconds() / (24.0 * 3600) - issue.working_hours_close = calendar.get_working_hours(dt_create_date, dt_date_closed, - compute_leaves=True, resource_id=False, default_interval=(8, 16)) + if issue.project_id.resource_calendar_id: + issue.working_hours_close = issue.project_id.resource_calendar_id.get_working_hours( + dt_create_date, dt_date_closed, + compute_leaves=True, resource_id=False) + else: + issue.working_hours_close = 0 @api.multi @api.depends('create_date', 'date_action_last', 'date_last_stage_update') diff --git a/addons/resource/models/resource.py b/addons/resource/models/resource.py index b6e4bcd6129..4867ca77294 100644 --- a/addons/resource/models/resource.py +++ b/addons/resource/models/resource.py @@ -191,11 +191,9 @@ class ResourceCalendar(models.Model): return attendances @api.multi - def get_weekdays(self, default_weekdays=None): - """ Return the list of weekdays that contain at least one working interval. - If no id is given (no calendar), return default weekdays. """ - if not self: - return default_weekdays if default_weekdays is not None else [0, 1, 2, 3, 4] + def get_weekdays(self): + """ Return the list of weekdays that contain at least one working + interval. """ self.ensure_one() weekdays = set(map(int, (self.attendance_ids.mapped('dayofweek')))) return list(weekdays) @@ -208,8 +206,6 @@ class ResourceCalendar(models.Model): :param date day_date: current day as a date :return date: next day of calendar, or just next day """ - if not self: - return day_date + relativedelta(days=1) self.ensure_one() weekdays = self.get_weekdays() @@ -234,8 +230,6 @@ class ResourceCalendar(models.Model): :param date day_date: current day as a date :return date: previous day of calendar, or just previous day """ - if not self: - return day_date + relativedelta(days=-1) self.ensure_one() weekdays = self.get_weekdays() weekdays.reverse() @@ -287,8 +281,7 @@ class ResourceCalendar(models.Model): @api.multi def get_working_intervals_of_day(self, start_dt=None, end_dt=None, - leaves=None, compute_leaves=False, resource_id=None, - default_interval=None): + leaves=None, compute_leaves=False, resource_id=None): """ Get the working intervals of the day based on calendar. This method handle leaves that come directly from the leaves parameter or can be computed. @@ -312,16 +305,10 @@ class ResourceCalendar(models.Model): computing the leaves. If not set, only general leaves are computed. If set, generic and specific leaves are computed. - :param tuple default_interval: if no id, try to return a default working - day using default_interval[0] as beginning - hour, and default_interval[1] as ending hour. - Example: default_interval = (8, 16). - Otherwise, a void list of working intervals - is returned when id is None. :return list intervals: a list of tuples (start_datetime, end_datetime) of work intervals """ - + self.ensure_one() # Computes start_dt, end_dt (with default values if not set) + off-interval work limits work_limits = [] if start_dt is None and end_dt is not None: @@ -339,15 +326,6 @@ class ResourceCalendar(models.Model): intervals = [] work_dt = start_dt.replace(hour=0, minute=0, second=0) - # no calendar: try to use the default_interval, then return directly - if not self: - working_interval = [] - if default_interval: - working_interval = (start_dt.replace(hour=default_interval[0], minute=0, second=0), - start_dt.replace(hour=default_interval[1], minute=0, second=0)) - intervals = self.interval_remove_leaves(working_interval, work_limits) - return intervals - working_intervals = [] tz_info = fields.Datetime.context_timestamp(self, work_dt).tzinfo for calendar_working_day in self.get_attendances_for_weekday(start_dt): @@ -375,23 +353,21 @@ class ResourceCalendar(models.Model): @api.multi def get_working_hours_of_date(self, start_dt=None, end_dt=None, - leaves=None, compute_leaves=False, resource_id=None, - default_interval=None): + leaves=None, compute_leaves=False, resource_id=None): """ Get the working hours of the day based on calendar. This method uses get_working_intervals_of_day to have the work intervals of the day. It then calculates the number of hours contained in those intervals. """ res = timedelta() intervals = self.get_working_intervals_of_day( start_dt, end_dt, leaves, - compute_leaves, resource_id, - default_interval) + compute_leaves, resource_id) for interval in intervals: res += interval[1] - interval[0] return seconds(res) / 3600.0 @api.multi def get_working_hours(self, start_dt, end_dt, compute_leaves=False, - resource_id=None, default_interval=None): + resource_id=None): hours = 0.0 for day in rrule.rrule(rrule.DAILY, dtstart=start_dt, @@ -405,8 +381,7 @@ class ResourceCalendar(models.Model): day_end_dt = end_dt hours += self.get_working_hours_of_date( start_dt=day_start_dt, end_dt=day_end_dt, - compute_leaves=compute_leaves, resource_id=resource_id, - default_interval=default_interval) + compute_leaves=compute_leaves, resource_id=resource_id) return hours # -------------------------------------------------- @@ -415,8 +390,7 @@ class ResourceCalendar(models.Model): @api.multi def _schedule_hours(self, hours, day_dt=None, - compute_leaves=False, resource_id=None, - default_interval=None): + compute_leaves=False, resource_id=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. @@ -433,12 +407,6 @@ class ResourceCalendar(models.Model): computing the leaves. If not set, only general leaves are computed. If set, generic and specific leaves are computed. - :param tuple default_interval: if no id, try to return a default working - day using default_interval[0] as beginning - hour, and default_interval[1] as ending hour. - Example: default_interval = (8, 16). - Otherwise, a void list of working intervals - is returned when id is None. :return tuple (datetime, intervals): datetime is the beginning/ending date of the schedulign; intervals are the @@ -447,6 +415,7 @@ class ResourceCalendar(models.Model): Note: Why not using rrule.rrule ? Because rrule does not seem to allow getting back in time. """ + self.ensure_one() if day_dt is None: day_dt = datetime.datetime.now() backwards = (hours < 0) @@ -456,7 +425,7 @@ class ResourceCalendar(models.Model): iterations = 0 current_datetime = day_dt - call_args = dict(compute_leaves=compute_leaves, resource_id=resource_id, default_interval=default_interval) + call_args = dict(compute_leaves=compute_leaves, resource_id=resource_id) while float_compare(remaining_hours, 0.0, precision_digits=2) in (1, 0) and iterations < 1000: if backwards: @@ -466,9 +435,7 @@ class ResourceCalendar(models.Model): working_intervals = self.get_working_intervals_of_day(**call_args) - if not self and not working_intervals: # no calendar -> consider working 8 hours - remaining_hours -= 8.0 - elif working_intervals: + if working_intervals: if backwards: working_intervals.reverse() new_working_intervals = self.interval_schedule_hours(working_intervals, remaining_hours, not backwards) @@ -495,20 +462,18 @@ class ResourceCalendar(models.Model): @api.multi def schedule_hours_get_date(self, hours, day_dt=None, - compute_leaves=False, resource_id=None, - default_interval=None): + compute_leaves=False, resource_id=None): """ Wrapper on _schedule_hours: return the beginning/ending datetime of an hours scheduling. """ - res = self._schedule_hours(hours, day_dt, compute_leaves, resource_id, default_interval) + res = self._schedule_hours(hours, day_dt, compute_leaves, resource_id) return res and res[0][0] or False @api.multi def schedule_hours(self, hours, day_dt=None, - compute_leaves=False, resource_id=None, - default_interval=None): + compute_leaves=False, resource_id=None): """ Wrapper on _schedule_hours: return the working intervals of an hours scheduling. """ - return self._schedule_hours(hours, day_dt, compute_leaves, resource_id, default_interval) + return self._schedule_hours(hours, day_dt, compute_leaves, resource_id) # -------------------------------------------------- # Days scheduling @@ -516,7 +481,7 @@ class ResourceCalendar(models.Model): @api.multi def _schedule_days(self, days, day_date=None, compute_leaves=False, - resource_id=None, default_interval=None): + resource_id=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. @@ -533,12 +498,6 @@ class ResourceCalendar(models.Model): computing the leaves. If not set, only general leaves are computed. If set, generic and specific leaves are computed. - :param tuple default_interval: if no id, try to return a default working - day using default_interval[0] as beginning - hour, and default_interval[1] as ending hour. - Example: default_interval = (8, 16). - Otherwise, a void list of working intervals - is returned when id is None. :return tuple (datetime, intervals): datetime is the beginning/ending date of the schedulign; intervals are the @@ -559,8 +518,7 @@ class ResourceCalendar(models.Model): while planned_days < days and iterations < 100: working_intervals = self.get_working_intervals_of_day( current_datetime, - compute_leaves=compute_leaves, resource_id=resource_id, - default_interval=default_interval) + compute_leaves=compute_leaves, resource_id=resource_id) if not self or working_intervals: # no calendar -> no working hours, but day is considered as worked planned_days += 1 intervals += working_intervals @@ -575,19 +533,17 @@ class ResourceCalendar(models.Model): return intervals @api.multi - def schedule_days_get_date(self, days, day_date=None, compute_leaves=False, - resource_id=None, default_interval=None): + def schedule_days_get_date(self, days, day_date=None, compute_leaves=False, resource_id=None): """ Wrapper on _schedule_days: return the beginning/ending datetime of a days scheduling. """ - res = self._schedule_days(days, day_date, compute_leaves, resource_id, default_interval) + res = self._schedule_days(days, day_date, compute_leaves, resource_id) return res and res[-1][1] or False @api.multi - def schedule_days(self, days, day_date=None, compute_leaves=False, - resource_id=None, default_interval=None): + def schedule_days(self, days, day_date=None, compute_leaves=False, resource_id=None): """ Wrapper on _schedule_days: return the working intervals of a days scheduling. """ - return self._schedule_days(days, day_date, compute_leaves, resource_id, default_interval) + return self._schedule_days(days, day_date, compute_leaves, resource_id) # -------------------------------------------------- # Compatibility / to clean / to remove @@ -612,8 +568,7 @@ class ResourceCalendar(models.Model): return self.schedule_hours( hours * -1.0, day_dt=dt_from.replace(minute=0, second=0), - compute_leaves=True, resource_id=resource, - default_interval=(8, 16) + compute_leaves=True, resource_id=resource ) @api.model @@ -629,7 +584,6 @@ class ResourceCalendar(models.Model): hours, day_dt=fields.Datetime.from_string(dt_str).replace(second=0), compute_leaves=True, resource_id=resource, - default_interval=(8, 16) ) res[(dt_str, hours, calendar_id)] = result return res @@ -660,7 +614,7 @@ class ResourceCalendar(models.Model): return self.get_working_hours( dt_from, dt_to, compute_leaves=(not exclude_leaves), resource_id=resource_id, - default_interval=(8, 16)) + ) class ResourceCalendarAttendance(models.Model): @@ -698,7 +652,7 @@ class ResourceResource(models.Model): @api.model def default_get(self, fields): res = super(ResourceResource, self).default_get(fields) - if not fields or 'calendar_id' in fields and not res.get('calendar_id') and res.get('company_id'): + if not res.get('calendar_id') and res.get('company_id'): company = self.env['res.company'].browse(res['company_id']) res['calendar_id'] = company.resource_calendar_id.id return res @@ -711,13 +665,23 @@ class ResourceResource(models.Model): company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env['res.company']._company_default_get()) resource_type = fields.Selection([ ('user', 'Human'), - ('material', 'Material') - ], string='Resource Type', required=True, default='user') + ('material', 'Material')], string='Resource Type', + default='user', required=True) user_id = fields.Many2one('res.users', string='User', help='Related user name for the resource to manage its access.') time_efficiency = fields.Float( 'Efficiency Factor', default=100, required=True, help="This field is used to calculate the the expected duration of a work order at this work center. For example, if a work order takes one hour and the efficiency factor is 100%, then the expected duration will be one hour. If the efficiency factor is 200%, however the expected duration will be 30 minutes.") - calendar_id = fields.Many2one("resource.calendar", string='Working Time', help="Define the schedule of resource") + calendar_id = fields.Many2one( + "resource.calendar", string='Working Time', + default=lambda self: self.env['res.company']._company_default_get().resource_calendar_id, + required=True, + help="Define the schedule of resource") + + @api.model + def create(self, values): + if values.get('company_id') and not values.get('calendar_id'): + values['calendar_id'] = self.env['res.company'].browse(values['company_id']).resource_calendar_id.id + return super(ResourceResource, self).create(values) @api.multi def copy(self, default=None): @@ -728,6 +692,11 @@ class ResourceResource(models.Model): default.update(name=_('%s (copy)') % (self.name)) return super(ResourceResource, self).copy(default) + @api.onchange('company_id') + def _onchange_company_id(self): + if self.company_id: + self.calendar_id = self.company_id.resource_calendar_id.id + def _is_work_day(self, date): """ Whether the provided date is a work day for the subject resource. diff --git a/addons/resource/tests/test_resource.py b/addons/resource/tests/test_resource.py index beafd44223e..528e5fac854 100644 --- a/addons/resource/tests/test_resource.py +++ b/addons/resource/tests/test_resource.py @@ -238,15 +238,6 @@ class ResourceWorkingHours(TestResourceCommon): compute_leaves=True, resource_id=self.resource1_id) self.assertEqual(res, 33.0) - def test_calendar_working_hours_default_calendar(self): - # Test without calendar and default_interval - res = self.env['resource.calendar'].with_context(tz='UTC').get_working_hours( - Datetime.from_string('2013-02-12 06:00:00'), - Datetime.from_string('2013-02-15 23:00:00'), - compute_leaves=True, resource_id=self.resource1_id, - default_interval=(8, 16)) - self.assertEqual(res, 32.0) - def test_calendar_hours_scheduling_backward(self): res = self.calendar.schedule_hours(-40, day_dt=Datetime.from_string('2013-02-12 09:00:00')) # current day, limited at 09:00 because of day_dt specified -> 1 hour @@ -312,11 +303,6 @@ class ResourceWorkingHours(TestResourceCommon): compute_leaves=True, resource_id=self.resource1_id) self.assertEqual(res.date(), Datetime.from_string('2013-03-01 00:00:00').date(), 'resource_calendar: wrong days scheduling') - def test_calendar_days_scheduling_without_calendar(self): - # Without calendar, should only count days -> 12 -> 16, 5 days with default intervals - res = self.env['resource.calendar'].with_context(tz='UTC').schedule_days_get_date(5, day_date=Datetime.from_string('2013-02-12 09:08:07'), default_interval=(8, 16)) - self.assertEqual(res, Datetime.from_string('2013-02-16 16:00:00'), 'resource_calendar: wrong days scheduling') - WAR_START = date(1932, 11, 2) WAR_END = date(1932, 12, 10) @@ -352,18 +338,6 @@ class TestWorkDays(TestResourceCommon): ) ] - def test_no_calendar(self): - """ If a resource has no resource calendar, they don't work """ - r = self.env['resource.resource'].create({ - 'name': "NoCalendar", - 'calendar_id': False, - }) - - self.assertEqual( - [], - list(r._iter_work_days(WAR_START, WAR_END)), - ) - def test_trivial_calendar_no_leaves(self): """ If leaves are not involved, only calendar attendances (basic company configuration) are taken in account