From 9fbbba1ee022c64a7566734db38ad4af5693bc52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 22 Jan 2014 14:29:18 +0100 Subject: [PATCH] [IMP] resource : some docstrings cleaning + void calendar taken into account in days scheduling + finished yesys bzr revid: tde@openerp.com-20140122132918-4c1ag6y5gqasi32d --- addons/resource/resource.py | 109 +++++++++++++++++-------- addons/resource/tests/common.py | 2 +- addons/resource/tests/test_resource.py | 59 +++++++++---- 3 files changed, 122 insertions(+), 48 deletions(-) diff --git a/addons/resource/resource.py b/addons/resource/resource.py index 3824f154681..8ee41c313cb 100644 --- a/addons/resource/resource.py +++ b/addons/resource/resource.py @@ -254,7 +254,7 @@ class resource_calendar(osv.osv): def get_working_intervals_of_day(self, cr, uid, id, start_dt=None, end_dt=None, leaves=None, compute_leaves=False, resource_id=None, - context=None): + context=None, default_interval=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. @@ -279,12 +279,16 @@ class resource_calendar(osv.osv): 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 """ - if id is None: - return [] if isinstance(id, (list, tuple)): id = id[0] @@ -305,6 +309,12 @@ class resource_calendar(osv.osv): intervals = [] work_dt = start_dt.replace(hour=0, minute=0, second=0) + # no calendar: try to use the default_interval, then return directly + if id is None: + if default_interval: + intervals.append((start_dt.replace(hour=default_interval[0]), start_dt.replace(hour=default_interval[1]))) + return intervals + working_intervals = [] for calendar_working_day in self.get_attendances_for_weekdays(cr, uid, id, [start_dt.weekday()], context): working_interval = ( @@ -336,6 +346,10 @@ class resource_calendar(osv.osv): res += interval[1] - interval[0] return (res.total_seconds() / 3600.0) + # -------------------------------------------------- + # Hours scheduling + # -------------------------------------------------- + def _schedule_hours(self, cr, uid, id, hours, day_dt=None, compute_leaves=False, resource_id=None, context=None): @@ -343,7 +357,7 @@ class resource_calendar(osv.osv): compute working and leave days. This method can be used backwards, i.e. scheduling days before a deadline. - :param timedelta hours: number of hours to schedule + :param int hours: number of hours to schedule :param datetime day_dt: reference date to compute working days. If days is > 0 date is the starting date. If days is < 0 date is the ending date. @@ -381,7 +395,7 @@ class resource_calendar(osv.osv): working_intervals = self.get_working_intervals_of_day(cr, uid, id, **call_args) - if id is None: # no calendar -> consider 8 working hours + if id is None: # no calendar -> consider working 8 hours remaining_hours -= 8.0 elif working_intervals: if backwards: @@ -419,7 +433,8 @@ class resource_calendar(osv.osv): scheduling. """ return self._schedule_hours(cr, uid, id, hours, day_dt, compute_leaves, resource_id, context) - def get_working_hours(self, cr, uid, id, start_dt, end_dt, compute_leaves=False, resource_id=None, context=None): + def get_working_hours(self, cr, uid, id, start_dt, end_dt, compute_leaves=False, + resource_id=None, context=None): hours = 0.0 for day in rrule.rrule(rrule.DAILY, dtstart=start_dt, until=end_dt + datetime.timedelta(days=1), @@ -427,11 +442,18 @@ class resource_calendar(osv.osv): hours += self.get_working_hours_of_date(cr, uid, id, start_dt=day, compute_leaves=compute_leaves, resource_id=resource_id) return hours - def _schedule_days(self, cr, uid, id, days, day_date=None, compute_leaves=False, resource_id=None, context=None): + # -------------------------------------------------- + # Days scheduling + # -------------------------------------------------- + + def _schedule_days(self, cr, uid, id, days, day_date=None, compute_leaves=False, + resource_id=None, default_interval=None, context=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. + :param int days: number of days to schedule. Use a negative number to compute + a backwards scheduling. :param date day_date: reference date to compute working days. If days is > 0 date is the starting date. If days is < 0 date is the ending date. @@ -442,13 +464,19 @@ class resource_calendar(osv.osv): 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 working intervals of the scheduling. - TDE NOTE: Why not using rrule.rrule ? Because rrule does not seem to - allow getting back in time. + Implementation note: rrule.rrule is not used because rrule it des not seem + to allow getting back in time. """ if day_date is None: day_date = datetime.datetime.now() @@ -463,7 +491,11 @@ class resource_calendar(osv.osv): current_datetime = day_date.replace(hour=0, minute=0, second=0) while planned_days < days and iterations < 1000: - working_intervals = self.get_working_intervals_of_day(cr, uid, id, current_datetime, compute_leaves=compute_leaves, resource_id=resource_id, context=context) + working_intervals = self.get_working_intervals_of_day( + cr, uid, id, current_datetime, + compute_leaves=compute_leaves, resource_id=resource_id, + context=context, + default_interval=default_interval) if id is None or working_intervals: # no calendar -> no working hours, but day is considered as worked planned_days += 1 intervals += working_intervals @@ -477,33 +509,37 @@ class resource_calendar(osv.osv): return intervals - def schedule_days_get_date(self, cr, uid, id, days, day_date=None, compute_leaves=False, resource_id=None, context=None): - """Wrapper on _schedule_days: return the beginning/ending datetime of + def schedule_days_get_date(self, cr, uid, id, days, day_date=None, compute_leaves=False, + resource_id=None, default_interval=None, context=None): + """ Wrapper on _schedule_days: return the beginning/ending datetime of a days scheduling. """ - res = self._schedule_days(cr, uid, id, days, day_date, compute_leaves, resource_id, context) - return res[-1][1] + res = self._schedule_days(cr, uid, id, days, day_date, compute_leaves, resource_id, default_interval, context) + return res and res[-1][1] or [] - def schedule_days(self, cr, uid, id, days, day_date=None, compute_leaves=False, resource_id=None, context=None): - """Wrapper on _schedule_days: return the working intervals of a days + def schedule_days(self, cr, uid, id, days, day_date=None, compute_leaves=False, + resource_id=None, default_interval=None, context=None): + """ Wrapper on _schedule_days: return the working intervals of a days scheduling. """ - return self._schedule_days(cr, uid, id, days, day_date, compute_leaves, resource_id, context) + return self._schedule_days(cr, uid, id, days, day_date, compute_leaves, resource_id, default_interval, context) # -------------------------------------------------- # Compatibility / to clean / to remove # -------------------------------------------------- def working_hours_on_day(self, cr, uid, resource_calendar_id, day, context=None): - """ Compatibility method - will be removed for OpenERP v8. - Computation was done for the whole day, ignoring hour/minutes. - Used in hr_payroll/hr_payroll.py """ + """ Used in hr_payroll/hr_payroll.py + + :deprecated: OpenERP saas-3. Use get_working_hours_of_date instead. Note: + since saas-3, take hour/minutes into account, not just the whole day.""" if isinstance(day, datetime.datetime): day = day.replace(hour=0, minute=0) return self.get_working_hours_of_date(cr, uid, resource_calendar_id.id, start_dt=day, context=None) def interval_min_get(self, cr, uid, id, dt_from, hours, resource=False): - """ Compatibility method - will be removed for OpenERP v8. - Schedule hours backwards. Note: now count leave hours instead of all-day leaves. - Used in mrp_operations/mrp_operations.py. """ + """ Schedule hours backwards. Used in mrp_operations/mrp_operations.py. + + :deprecated: OpenERP saas-3. Use schedule_hours instead. Note: since + saas-3, counts leave hours instead of all-day leaves.""" return self.schedule_hours( cr, uid, id, hours * -1.0, day_dt=dt_from.replace(minute=0, second=0), @@ -511,9 +547,11 @@ class resource_calendar(osv.osv): ) def interval_get_multi(self, cr, uid, date_and_hours_by_cal, resource=False, byday=True): - """ Compatibility method - will be removed for OpenERP v8. - Note: used in mrp_operations/mrp_operations.py (default parameters) and in interval_get() - Byday was not used. Now counts Leave hours instead of all-day leaves. """ + """ Used in mrp_operations/mrp_operations.py (default parameters) and in + interval_get() + + :deprecated: OpenERP saas-3. Use get_working_hours_of_date instead. Note: + Byday was not used. Since saas-3, counts Leave hours instead of all-day leaves.""" res = {} for dt_str, hours, calendar_id in date_and_hours_by_cal: result = self.schedule_hours( @@ -525,20 +563,25 @@ class resource_calendar(osv.osv): return res def interval_get(self, cr, uid, id, dt_from, hours, resource=False, byday=True): - """ Compatibility method - will be removed for OpenERP v8. Unifier of interval_get_multi. - Used in: mrp_operations/mrp_operations.py, crm/crm_lead.py (res given) """ + """ Unifier of interval_get_multi. Used in: mrp_operations/mrp_operations.py, + crm/crm_lead.py (res given). + + :deprecated: OpenERP saas-3. Use get_working_hours instead.""" res = self.interval_get_multi(cr, uid, [(dt_from.strftime('%Y-%m-%d %H:%M:%S'), hours, id)], resource, byday)[(dt_from.strftime('%Y-%m-%d %H:%M:%S'), hours, id)] return res def interval_hours_get(self, cr, uid, id, dt_from, dt_to, resource=False): - """ Compatibility method - will be removed for OpenERP v8. Unused wrapper """ + """ Unused wrapper. + + :deprecated: OpenERP saas-3. Use get_working_hours instead.""" return self._interval_hours_get(cr, uid, id, dt_from, dt_to, resource_id=resource) def _interval_hours_get(self, cr, uid, id, dt_from, dt_to, resource_id=False, timezone_from_uid=None, exclude_leaves=True, context=None): - """ Compatibility method - will be removed for OpenERP v8. - Computes working hours between two dates, taking always same hour/minuts. - Note: now resets hour/minuts. Now counts leave hours instead of all-day leaves. """ - return self.get_working_hours(cr, uid, id, dt_from, dt_to, compute_leaves=exclude_leaves, resource_id=resource_id, context=context) + """ Computes working hours between two dates, taking always same hour/minuts. + + :deprecated: OpenERP saas-3. Use get_working_hours instead. Note: since saas-3, + now resets hour/minuts. Now counts leave hours instead of all-day leaves.""" + return self.get_working_hours(cr, uid, id, dt_from, dt_to, compute_leaves=(not exclude_leaves), resource_id=resource_id, context=context) class resource_calendar_attendance(osv.osv): diff --git a/addons/resource/tests/common.py b/addons/resource/tests/common.py index e5d17bc7455..74b91abde2e 100644 --- a/addons/resource/tests/common.py +++ b/addons/resource/tests/common.py @@ -52,7 +52,7 @@ class TestResourceCommon(common.TransactionCase): self.leave3_end = datetime.strptime('2013-03-01 11:30:00', '%Y-%m-%d %H:%M:%S') # Resource data - # Calendar working days: 1 (8-16), 4 (8-13, 21-22) + # Calendar working days: 1 (8-16 -> 8hours), 4 (8-13, 16-23 -> 12hours) self.calendar_id = self.resource_calendar.create( cr, uid, { 'name': 'TestCalendar', diff --git a/addons/resource/tests/test_resource.py b/addons/resource/tests/test_resource.py index 1413c6732c6..ad06a1f2430 100644 --- a/addons/resource/tests/test_resource.py +++ b/addons/resource/tests/test_resource.py @@ -293,7 +293,7 @@ class TestResource(TestResourceCommon): self.assertEqual(td.total_seconds() / 3600.0, 40.0, 'resource_calendar: wrong hours scheduling') # -------------------------------------------------- - # Test1: schedule hours forward (old interval_get) + # Test2: schedule hours forward (old interval_get) # -------------------------------------------------- # res = self.resource_calendar.interval_get(cr, uid, self.calendar_id, self.date1, 40, resource=False, byday=True) @@ -366,23 +366,55 @@ class TestResource(TestResourceCommon): td += item[1] - item[0] self.assertEqual(td.total_seconds() / 3600.0, 40.0, 'resource_calendar: wrong hours scheduling') - res = self.resource_calendar._interval_hours_get(cr, uid, self.calendar_id, self.date1.replace(hour=6, minute=0), self.date2.replace(hour=23, minute=0) + relativedelta(days=7), resource_id=self.resource1_id, exclude_leaves=False) - # print res + # -------------------------------------------------- + # Test3: working hours (old _interval_hours_get) + # -------------------------------------------------- - res = self.resource_calendar.get_working_hours(cr, uid, self.calendar_id, self.date1.replace(hour=6, minute=0), self.date2.replace(hour=23, minute=0) + relativedelta(days=7), compute_leaves=False, resource_id=self.resource1_id) - # print res + # old API: resource without leaves + # res: 2 weeks -> 40 hours + res = self.resource_calendar._interval_hours_get( + cr, uid, self.calendar_id, + self.date1.replace(hour=6, minute=0), + self.date2.replace(hour=23, minute=0) + relativedelta(days=7), + resource_id=self.resource1_id, exclude_leaves=True) + self.assertEqual(res, 40.0, 'resource_calendar: wrong _interval_hours_get compatibility computation') - res = self.resource_calendar._interval_hours_get(cr, uid, self.calendar_id, self.date1.replace(hour=6, minute=0), self.date2.replace(hour=23, minute=0) + relativedelta(days=7), resource_id=self.resource1_id, exclude_leaves=True) - # print res + # new API: resource without leaves + # res: 2 weeks -> 40 hours + res = self.resource_calendar.get_working_hours( + cr, uid, self.calendar_id, + self.date1.replace(hour=6, minute=0), + self.date2.replace(hour=23, minute=0) + relativedelta(days=7), + compute_leaves=False, resource_id=self.resource1_id) + self.assertEqual(res, 40.0, 'resource_calendar: wrong get_working_hours computation') - res = self.resource_calendar.get_working_hours(cr, uid, self.calendar_id, self.date1.replace(hour=6, minute=0), self.date2.replace(hour=23, minute=0) + relativedelta(days=7), compute_leaves=True, resource_id=self.resource1_id) - # print res + # old API: resource and leaves + # res: 2 weeks -> 40 hours - (3+4) leave hours + res = self.resource_calendar._interval_hours_get( + cr, uid, self.calendar_id, + self.date1.replace(hour=6, minute=0), + self.date2.replace(hour=23, minute=0) + relativedelta(days=7), + resource_id=self.resource1_id, exclude_leaves=False) + self.assertEqual(res, 33.0, 'resource_calendar: wrong _interval_hours_get compatibility computation') + + # new API: resource and leaves + # res: 2 weeks -> 40 hours - (3+4) leave hours + res = self.resource_calendar.get_working_hours( + cr, uid, self.calendar_id, + self.date1.replace(hour=6, minute=0), + self.date2.replace(hour=23, minute=0) + relativedelta(days=7), + compute_leaves=True, resource_id=self.resource1_id) + self.assertEqual(res, 33.0, 'resource_calendar: wrong get_working_hours computation') def test_50_calendar_schedule_days(self): """ Testing calendar days scheduling """ cr, uid = self.cr, self.uid _format = '%Y-%m-%d %H:%M:%S' + # -------------------------------------------------- + # Test1: with calendar + # -------------------------------------------------- + res = self.resource_calendar.schedule_days_get_date(cr, uid, self.calendar_id, 5, day_date=self.date1) self.assertEqual(res.date(), datetime.strptime('2013-02-26 00:0:00', _format).date(), 'resource_calendar: wrong days scheduling') @@ -392,10 +424,9 @@ class TestResource(TestResourceCommon): self.assertEqual(res.date(), datetime.strptime('2013-03-01 00:0:00', _format).date(), 'resource_calendar: wrong days scheduling') # -------------------------------------------------- - # Misc + # Test2: without calendar and default scheduling # -------------------------------------------------- - # # Without calendar, should only count days - # print '---------------' - # res = self.resource_calendar.schedule_days_get_date(cr, uid, None, 5, day_date=self.date1) - # print res + # Without calendar, should only count days -> 12 -> 16, 5 days + res = self.resource_calendar.schedule_days_get_date(cr, uid, None, 5, day_date=self.date1, default_interval=(8, 16)) + self.assertEqual(res, datetime.strptime('2013-02-16 16:00:00', _format), 'resource_calendar: wrong days scheduling')