From a17092ba94b1be75c76e1cc691674c8809b213c1 Mon Sep 17 00:00:00 2001 From: Julien Thewys Date: Sun, 7 Mar 2010 17:49:07 +0100 Subject: [PATCH 01/19] [REF] Renamed file and moved import statement. bzr revid: jth@openerp.com-20100307164907-t1cevokp7u1zz2tx --- bin/tools/__init__.py | 1 - bin/tools/convert.py | 1 + bin/tools/{yaml_test.py => yaml_import.py} | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename bin/tools/{yaml_test.py => yaml_import.py} (99%) diff --git a/bin/tools/__init__.py b/bin/tools/__init__.py index 78c77edf394..9b7bb84a57f 100644 --- a/bin/tools/__init__.py +++ b/bin/tools/__init__.py @@ -24,7 +24,6 @@ import win32 from config import * from misc import * from convert import * -from yaml_test import * from translate import * from graph import graph from amount_to_text import * diff --git a/bin/tools/convert.py b/bin/tools/convert.py index db3edf380f7..2e16237f50a 100644 --- a/bin/tools/convert.py +++ b/bin/tools/convert.py @@ -37,6 +37,7 @@ import netsvc import osv import pooler from config import config +from yaml_import import convert_yaml_import class ConvertError(Exception): def __init__(self, doc, orig_excpt): diff --git a/bin/tools/yaml_test.py b/bin/tools/yaml_import.py similarity index 99% rename from bin/tools/yaml_test.py rename to bin/tools/yaml_import.py index b4d23376c7a..8602afd58cb 100644 --- a/bin/tools/yaml_test.py +++ b/bin/tools/yaml_import.py @@ -368,7 +368,7 @@ class YamlInterpreter(object): model = self.get_model(record.model) record_dict = self._create_record(model, fields) id = self.pool.get('ir.model.data')._update(self.cr, self.uid, record.model, \ - self.module, record_dict, record.id, noupdate=self.isnoupdate(node), mode=self.mode) + self.module, record_dict, record.id, noupdate=self.isnoupdate(record), mode=self.mode) self.id_map[record.id] = int(id) if config.get('import_partial', False): self.cr.commit() From f9e6517ee6ec96cdf467f9198261dfc25d730d2e Mon Sep 17 00:00:00 2001 From: Julien Thewys Date: Sun, 7 Mar 2010 22:18:33 +0100 Subject: [PATCH 02/19] [REF] Uses logging.getLogger instead of the to be deprecated netsvc.Logger . bzr revid: jth@openerp.com-20100307211833-j4b6x8pcbirs0sxk --- bin/tools/yaml_import.py | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/bin/tools/yaml_import.py b/bin/tools/yaml_import.py index 8602afd58cb..2c334c9cd3c 100644 --- a/bin/tools/yaml_import.py +++ b/bin/tools/yaml_import.py @@ -31,7 +31,7 @@ class YamlTag(object): return "<%s %s>" % (self.__class__.__name__, sorted(self.__dict__.items())) class Assert(YamlTag): - def __init__(self, model, id, severity=netsvc.LOG_ERROR, string="NONAME", **kwargs): + def __init__(self, model, id, severity=logging.ERROR, string="NONAME", **kwargs): self.model = model self.id = id self.severity = severity @@ -45,7 +45,7 @@ class Record(YamlTag): super(Record, self).__init__(**kwargs) class Python(YamlTag): - def __init__(self, model, severity=netsvc.LOG_ERROR, name="NONAME", **kwargs): + def __init__(self, model, severity=logging.ERROR, name="NONAME", **kwargs): self.model= model self.severity = severity self.name = name @@ -259,7 +259,7 @@ class YamlInterpreter(object): self.filename = filename self.assert_report = TestReport() self.noupdate = noupdate - self.logger = netsvc.Logger() + self.logger = logging.getLogger(logger_channel) self.pool = pooler.get_pool(cr.dbname) self.uid = 1 self.context = {} @@ -284,7 +284,7 @@ class YamlInterpreter(object): ['&', ('name', '=', module), ('state', 'in', ['installed'])]) assert module_count == 1, """The ID "%s" refers to an uninstalled module""" % (xml_id,) if len(id) > 64: # TODO where does 64 come from (DB is 128)? should be a constant or loaded form DB - self.logger.notifyChannel(logger_channel, netsvc.LOG_ERROR, 'id: %s is to long (max: 64)' % (id,)) + self.logger.log(logging.ERROR, 'id: %s is to long (max: 64)', id) def get_id(self, xml_id): if isinstance(xml_id, types.IntType): @@ -316,8 +316,8 @@ class YamlInterpreter(object): def _log_assert_failure(self, severity, msg, *args): self.assert_report.record(False, severity) - self.logger.notifyChannel(logger_channel, severity, msg % (args)) - if getattr(logging, severity.upper()) >= config['assert_exit_level']: + self.logger.log(severity, msg, *args) + if severity >= config['assert_exit_level']: raise YamlImportAbortion('Severe assertion failure (%s), aborting.' % (severity,)) return @@ -404,8 +404,8 @@ class YamlInterpreter(object): self.uid = self.get_id(node.__dict__['uid']) def process_python(self, node): - def log(msg): - self.logger.notifyChannel(logger_channel, netsvc.LOG_TEST, msg) + def log(msg, *args): + self.logger.log(logging.TEST, msg, *args) python, statements = node.items()[0] model = self.get_model(python.model) statements = statements.replace("\r\n", "\n") @@ -414,9 +414,7 @@ class YamlInterpreter(object): code = compile(statements, self.filename, 'exec') eval(code, code_context) except AssertionError, e: - msg = 'Assertion "%s" FAILED in Python code.' - args = (python.name,) - self._log_assert_failure(python.severity, msg, *args) + self._log_assert_failure(python.severity, 'Assertion "%s" FAILED in Python code.', python.name) return except Exception, e: raise YamlImportAbortion(e) @@ -693,9 +691,9 @@ class YamlInterpreter(object): try: self._process_node(node) except YamlImportException, e: - self.logger.notifyChannel(logger_channel, netsvc.LOG_ERROR, e) + self.logger.log(logging.ERROR, e) except YamlImportAbortion, e: - self.logger.notifyChannel(logger_channel, netsvc.LOG_ERROR, e) + self.logger.log(logging.ERROR, e) return def _process_node(self, node): @@ -737,14 +735,14 @@ class YamlInterpreter(object): def _log(self, node, is_preceded_by_comment): if is_comment(node): is_preceded_by_comment = True - self.logger.notifyChannel(logger_channel, netsvc.LOG_TEST, node) + self.logger.log(logging.TEST, node) elif not is_preceded_by_comment: if isinstance(node, types.DictionaryType): - k, v = node.items()[0] - msg = "Creating %s\n with %s" % (k, v) - self.logger.notifyChannel(logger_channel, netsvc.LOG_TEST, msg) + msg = "Creating %s\n with %s" + args = node.items()[0] + self.logger.log(logging.TEST, msg, *args) else: - self.logger.notifyChannel(logger_channel, netsvc.LOG_TEST, node) + self.logger.log(logging.TEST, node) else: is_preceded_by_comment = False return is_preceded_by_comment From 4bc27ab050a94729e005db244343ca19d52ecc27 Mon Sep 17 00:00:00 2001 From: Mantavya Gajjar Date: Mon, 8 Mar 2010 12:11:15 +0530 Subject: [PATCH 03/19] [MERGE]: merging a code to improve report thanks to dsh for code bzr revid: mga@tinyerp.com-20100308064115-f1v61wefu04balsx --- bin/addons/base/res/res_lang.py | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/bin/addons/base/res/res_lang.py b/bin/addons/base/res/res_lang.py index d45824cc9ec..8a3af7dd175 100644 --- a/bin/addons/base/res/res_lang.py +++ b/bin/addons/base/res/res_lang.py @@ -21,6 +21,7 @@ from osv import fields, osv from locale import localeconv +import tools class lang(osv.osv): _name = "res.lang" @@ -59,23 +60,35 @@ class lang(osv.osv): ('name_uniq', 'unique (name)', 'The name of the language must be unique !'), ('code_uniq', 'unique (code)', 'The code of the language must be unique !'), ] - def _group(self,cr,uid,ids,s, monetary=False): + + @tools.cache(skiparg=3) + def _lang_data_get(self, cr, uid, lang_id): conv = localeconv() - - lang_obj=self.browse(cr,uid,ids[0]) + lang_obj=self.browse(cr,uid,lang_id) thousands_sep = lang_obj.thousands_sep or conv[monetary and 'mon_thousands_sep' or 'thousands_sep'] + decimal_point = lang_obj.decimal_point + grouping = lang_obj.grouping + return (grouping, thousands_sep, decimal_point) - grouping = eval(lang_obj.grouping) + def write(self, cr, uid, ids, vals, context=None): + for lang_id in ids : + self._lang_data_get.clear_cache(cr.dbname,lang_id= lang_id) + return super(lang, self).write(cr, uid, ids, vals, context) + def _group(self, cr, uid, ids, s, monetary=False, grouping=False, thousands_sep=''): + grouping = eval(grouping) + if not grouping: return (s, 0) result = "" seps = 0 spaces = "" + if s[-1] == ' ': sp = s.find(' ') spaces = s[sp:] s = s[:sp] + while s and grouping: # if grouping is -1, we are done if grouping[0] == -1: @@ -101,13 +114,13 @@ class lang(osv.osv): seps += 1 return result + spaces, seps - def format(self,cr,uid,ids,percent, value, grouping=False, monetary=False): + def format(self, cr, uid, ids, percent, value, grouping=False, monetary=False): """ Format() will return the language-specific output for float values""" if percent[0] != '%': raise ValueError("format() must be given exactly one %char format specifier") - lang_obj=self.browse(cr,uid,ids[0]) + lang_grouping, thousands_sep, decimal_point = self._lang_data_get(cr, uid, ids[0]) formatted = percent % value # floats and decimal ints need special action! @@ -116,9 +129,8 @@ class lang(osv.osv): parts = formatted.split('.') if grouping: - parts[0], seps = self._group(cr,uid,ids,parts[0], monetary=monetary) + parts[0], seps = self._group(cr,uid,ids,parts[0], monetary=monetary, grouping=lang_grouping, thousands_sep=thousands_sep) - decimal_point=lang_obj.decimal_point formatted = decimal_point.join(parts) while seps: sp = formatted.find(' ') @@ -127,7 +139,7 @@ class lang(osv.osv): seps -= 1 elif percent[-1] in 'diu': if grouping: - formatted = self._group(cr,uid,ids,formatted, monetary=monetary)[0] + formatted = self._group(cr,uid,ids,formatted, monetary=monetary, grouping=lang_grouping, thousands_sep=thousands_sep)[0] return formatted From 325bcd1c5b5cb44f3165287620b957ea68f18537 Mon Sep 17 00:00:00 2001 From: "nch@tinyerp.com" <> Date: Mon, 8 Mar 2010 13:59:39 +0530 Subject: [PATCH 04/19] [IMP]:change the way to calculate avg/sum bzr revid: nch@tinyerp.com-20100308082939-crtgxay16njypjt7 --- bin/osv/orm.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/bin/osv/orm.py b/bin/osv/orm.py index 0165d4e7a4a..5904ab5cf3a 100644 --- a/bin/osv/orm.py +++ b/bin/osv/orm.py @@ -1871,13 +1871,9 @@ class orm(orm_template): fields_pre = [f for f in float_int_fields if f == self.CONCURRENCY_CHECK_FIELD or (f in self._columns and getattr(self._columns[f], '_classic_write'))] - avg_fields = context.get('avg',[]) for f in fields_pre: if f not in ['id','sequence']: - if f in avg_fields: - flist += ',avg('+f+') as '+f - else: - flist += ',sum('+f+') as '+f + flist += ',sum('+f+') as '+f cr.execute('select min(%s.id) as id,' % self._table + flist + ' from ' + ','.join(tables) + where_clause + ' group by '+ groupby + limit_str + offset_str, where_params) alldata = {} From 546b76d5d3ed95af3f8d7d38504f25732baac4da Mon Sep 17 00:00:00 2001 From: Julien Thewys Date: Mon, 8 Mar 2010 09:55:32 +0100 Subject: [PATCH 05/19] [REF] Reoarganized imports, catches yaml not found. bzr revid: jth@openerp.com-20100308085532-1yv9xv1tosz223vs --- bin/tools/yaml_import.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/bin/tools/yaml_import.py b/bin/tools/yaml_import.py index 2c334c9cd3c..9632842bc80 100644 --- a/bin/tools/yaml_import.py +++ b/bin/tools/yaml_import.py @@ -1,15 +1,20 @@ # -*- encoding: utf-8 -*- import types -import yaml import time # used to eval time.strftime expressions import logging import pooler import netsvc import misc - from config import config +try: + import yaml +except ImportError, e: + msg = "Module yaml was not found." + e.args = (msg,) + raise e + logger_channel = 'tests' class YamlImportException(Exception): From 8856a549f738f983795d29da330044568147828a Mon Sep 17 00:00:00 2001 From: "nch@tinyerp.com" <> Date: Mon, 8 Mar 2010 15:10:36 +0530 Subject: [PATCH 06/19] [IMP]:Avg/sum for groupby bzr revid: nch@tinyerp.com-20100308094036-hpvzsvlygup7s0e7 --- bin/osv/fields.py | 20 +++++++++++--------- bin/osv/orm.py | 5 +++-- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/bin/osv/fields.py b/bin/osv/fields.py index 5d373a168ea..ce02de20a27 100644 --- a/bin/osv/fields.py +++ b/bin/osv/fields.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- ############################################################################## -# +# # OpenERP, Open Source Management Solution # Copyright (C) 2004-2009 Tiny SPRL (). # @@ -15,7 +15,7 @@ # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . +# along with this program. If not, see . # ############################################################################## @@ -80,6 +80,7 @@ class _column(object): self.view_load = 0 self.select = select self.selectable = True + self.group_operator = args.get('group_operator', False) for a in args: if args[a]: setattr(self, a, args[a]) @@ -177,6 +178,7 @@ class float(_column): self.digits = digits self.digits_compute = digits_compute + def digits_change(self, cr): if self.digits_compute: t = self.digits_compute(cr) @@ -320,8 +322,8 @@ class many2one(_column): except except_orm: record_name = {} record_name[record] = '// Access Denied //' - names.update(record_name) - + names.update(record_name) + for r in res.keys(): if res[r] and res[r] in names: res[r] = (res[r], names[res[r]]) @@ -629,7 +631,7 @@ class function(_column): self._multi = multi if 'relation' in args: self._obj = args['relation'] - + self.digits = args.get('digits', (16,2)) self.digits_compute = args.get('digits_compute', None) @@ -642,7 +644,7 @@ class function(_column): if not fnct_search and not store: self.selectable = False - + if store: self._classic_read = True self._classic_write = True @@ -681,14 +683,14 @@ class function(_column): if self._type == "many2one" : # Filtering only integer/long values if passed res_ids = [x for x in res.values() if x and isinstance(x, (int,long))] - + if res_ids: obj_model = obj.pool.get(self._obj) dict_names = dict(obj_model.name_get(cr, user, res_ids, context)) for r in res.keys(): if res[r] and res[r] in dict_names: res[r] = (res[r], dict_names[res[r]]) - + if self._type == 'binary' and context.get('bin_size', False): # convert the data returned by the function with the size of that data... res = dict(map( get_nice_size, res.items())) @@ -838,7 +840,7 @@ class dummy(function): def _fnct_read(self, obj, cr, uid, ids, field_name, args, context=None): return {} - + def __init__(self, *arg, **args): self.arg = arg self._relations = [] diff --git a/bin/osv/orm.py b/bin/osv/orm.py index 5904ab5cf3a..7f412be0a7a 100644 --- a/bin/osv/orm.py +++ b/bin/osv/orm.py @@ -1007,7 +1007,7 @@ class orm_template(object): if fields and f not in fields: continue res[f] = {'type': self._columns[f]._type} - for arg in ('string', 'readonly', 'states', 'size', 'required', + for arg in ('string', 'readonly', 'states', 'size', 'required', 'group_operator', 'change_default', 'translate', 'help', 'select', 'selectable'): if getattr(self._columns[f], arg): res[f][arg] = getattr(self._columns[f], arg) @@ -1873,7 +1873,8 @@ class orm(orm_template): or (f in self._columns and getattr(self._columns[f], '_classic_write'))] for f in fields_pre: if f not in ['id','sequence']: - flist += ',sum('+f+') as '+f + operator = fget[f].get('group_operator','sum') + flist += ','+operator+'('+f+') as '+f cr.execute('select min(%s.id) as id,' % self._table + flist + ' from ' + ','.join(tables) + where_clause + ' group by '+ groupby + limit_str + offset_str, where_params) alldata = {} From f36e7da6a81d8da5a51fd7ce96fb9757e9a18aa7 Mon Sep 17 00:00:00 2001 From: "Jay (Open ERP)" Date: Mon, 8 Mar 2010 15:52:15 +0530 Subject: [PATCH 07/19] [FIX] Base : Event Type form view should have key field editable lp bug: https://launchpad.net/bugs/531347 fixed bzr revid: jvo@tinyerp.com-20100308102215-k6yn58vks1th7sjw --- bin/addons/base/res/partner/crm_view.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/addons/base/res/partner/crm_view.xml b/bin/addons/base/res/partner/crm_view.xml index 4acb9004293..da4e08c7644 100644 --- a/bin/addons/base/res/partner/crm_view.xml +++ b/bin/addons/base/res/partner/crm_view.xml @@ -30,7 +30,7 @@
- + From 6307322cdbd471c1c9377a80eedf408a0366844a Mon Sep 17 00:00:00 2001 From: Julien Thewys Date: Mon, 8 Mar 2010 12:29:28 +0100 Subject: [PATCH 08/19] [REF] Reorganized imports (and undo previous non-sense change). bzr revid: jth@openerp.com-20100308112928-ewsboa2xxnuot4wi --- bin/tools/yaml_import.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/bin/tools/yaml_import.py b/bin/tools/yaml_import.py index 9632842bc80..8296a205830 100644 --- a/bin/tools/yaml_import.py +++ b/bin/tools/yaml_import.py @@ -8,12 +8,7 @@ import netsvc import misc from config import config -try: - import yaml -except ImportError, e: - msg = "Module yaml was not found." - e.args = (msg,) - raise e +import yaml logger_channel = 'tests' From a8d9eb604f59e5a282f20618cb450b419228a517 Mon Sep 17 00:00:00 2001 From: "noz@tinyerp.com" <> Date: Mon, 8 Mar 2010 17:23:00 +0530 Subject: [PATCH 09/19] [IMP] Added HTML View for Form view (example code). bzr revid: noz@tinyerp.com-20100308115300-su95aa69otmzx2ke --- bin/addons/base/res/partner/partner_view.xml | 37 ++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/bin/addons/base/res/partner/partner_view.xml b/bin/addons/base/res/partner/partner_view.xml index 91c7532219e..6d8e6b00b19 100644 --- a/bin/addons/base/res/partner/partner_view.xml +++ b/bin/addons/base/res/partner/partner_view.xml @@ -251,10 +251,43 @@ res.partner form -
+ + +
+ HTML Form Example +
+ + + + + +
+ + + + + + +
+ + OpenERP + + + + Tiny + +
+
+
+ - From d1c37a80b646be2a38c804e1534ff52423fb8ba2 Mon Sep 17 00:00:00 2001 From: "mra (Open ERP)" Date: Tue, 9 Mar 2010 11:13:27 +0530 Subject: [PATCH 10/19] [FIX] event: Registration by events: you can create a new event since list view, but the one is not editable lp bug: https://launchpad.net/bugs/534493 fixed bzr revid: mra@tinyerp.com-20100309054327-9oynsicikp90ort7 --- addons/event/event_view.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/event/event_view.xml b/addons/event/event_view.xml index 92c08594c11..55dea914ac5 100644 --- a/addons/event/event_view.xml +++ b/addons/event/event_view.xml @@ -338,7 +338,7 @@ - + From 1e8622118905179e2a1f665dabe44d0abad15b4c Mon Sep 17 00:00:00 2001 From: "mra (Open ERP)" Date: Tue, 9 Mar 2010 12:06:03 +0530 Subject: [PATCH 11/19] [FIX] hr_timesheet_sheet: to link attendaces with timesheet (employee now attchaed with resource so change the query) bzr revid: mra@tinyerp.com-20100309063603-3sxf2efirreqoneq --- addons/hr_timesheet_sheet/hr_timesheet_sheet.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/addons/hr_timesheet_sheet/hr_timesheet_sheet.py b/addons/hr_timesheet_sheet/hr_timesheet_sheet.py index a738ee55ace..b7d395ac2e0 100644 --- a/addons/hr_timesheet_sheet/hr_timesheet_sheet.py +++ b/addons/hr_timesheet_sheet/hr_timesheet_sheet.py @@ -482,7 +482,7 @@ class hr_attendance(osv.osv): res[line_id] = False return res - def _sheet_search(self, cursor, user, obj, name, args): + def _sheet_search(self, cursor, user, obj, name, args, context={}): if not len(args): return [] sheet_obj = self.pool.get('hr_timesheet_sheet.sheet') @@ -525,9 +525,11 @@ class hr_attendance(osv.osv): LEFT JOIN (hr_attendance a \ LEFT JOIN hr_employee e \ ON (a.employee_id = e.id)) \ + LEFT JOIN resource_resource r \ + ON (e.resource_id = r.id) \ ON (s.date_to >= date_trunc(\'day\',a.name) \ AND s.date_from <= a.name \ - AND s.user_id = e.user_id) ' + \ + AND s.user_id = r.user_id) ' + \ qu1, qu2) res = cursor.fetchall() if not len(res): From 836636905c9356c7cc0b4133616b66dbe63c061c Mon Sep 17 00:00:00 2001 From: "HDA (OpenERP)" Date: Tue, 9 Mar 2010 12:58:46 +0530 Subject: [PATCH 12/19] [IMP] scrum: improved burndown chart bzr revid: hda@tinyerp.com-20100309072846-5mylm9v12z60lt7m --- addons/scrum/report/__init__.py | 1 - addons/scrum/report/sprint_burndown.py | 16 +++++++++++----- addons/scrum/scrum_report.xml | 6 +----- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/addons/scrum/report/__init__.py b/addons/scrum/report/__init__.py index 2e7bbe68cd0..6112b117959 100644 --- a/addons/scrum/report/__init__.py +++ b/addons/scrum/report/__init__.py @@ -19,7 +19,6 @@ # ############################################################################## -import task_burndown import sprint_burndown # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/scrum/report/sprint_burndown.py b/addons/scrum/report/sprint_burndown.py index 79a549ff81f..cdc837fe96b 100644 --- a/addons/scrum/report/sprint_burndown.py +++ b/addons/scrum/report/sprint_burndown.py @@ -50,9 +50,6 @@ class report_tasks(report_int): cr.execute('select id,date_start,date_stop from scrum_sprint where id=%s', (datas['id'],)) for (id,date_start,date_stop) in cr.fetchall(): - date_to_int = lambda x: int(x.ticks()) - int_to_date = lambda x: '/a60{}'+DateTime.localtime(x).strftime('%d/%m/%Y') - cr.execute('select id from project_task where product_backlog_id in(select id from scrum_product_backlog where sprint_id=%s)', (id,)) ids = map(lambda x: x[0], cr.fetchall()) @@ -71,7 +68,9 @@ class report_tasks(report_int): if (not result) or result[-1]<>res: result.append(res) return result - + + guideline__data=[(datas[0][0],max_hour), (datas[-1][0],0)] + ar = area.T(x_grid_style=line_style.gray50_dash1, x_axis=axis.X(label="Date", format=int_to_date), y_axis=axis.Y(label="Burndown Chart - Planned Hours"), @@ -80,7 +79,14 @@ class report_tasks(report_int): y_range = (0,max_hour), legend = None, size = (680,450)) - ar.add_plot(line_plot.T(data=datas)) + ar.add_plot(line_plot.T(data=guideline__data, line_style=line_style.red)) + ar.add_plot(line_plot.T(data=datas, line_style=line_style.green)) + + entr1 = pychart.legend.Entry(label="guideline", line_style=line_style.red) + entr2 = pychart.legend.Entry(label="burndownchart",line_style=line_style.green) + legend = pychart.legend.T(nr_rows=2, inter_row_sep=5) + legend.draw(ar,[entr1,entr2],canv) + ar.draw(canv) canv.close() diff --git a/addons/scrum/scrum_report.xml b/addons/scrum/scrum_report.xml index a5b3a672986..845a677c356 100644 --- a/addons/scrum/scrum_report.xml +++ b/addons/scrum/scrum_report.xml @@ -1,10 +1,6 @@ - - - - + - From 33c94dc95f8bd9283864d43a15c4c8fc4c836faf Mon Sep 17 00:00:00 2001 From: "noz@tinyerp.com" <> Date: Tue, 9 Mar 2010 13:58:27 +0530 Subject: [PATCH 13/19] [FIX] Wrong context syntax. bzr revid: noz@tinyerp.com-20100309082827-w33bvmg1dsoyzcgu --- addons/account/partner_view.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/account/partner_view.xml b/addons/account/partner_view.xml index 44cf3afde69..1b4603cd8ed 100644 --- a/addons/account/partner_view.xml +++ b/addons/account/partner_view.xml @@ -91,7 +91,7 @@ - + From 03bce49375554543318d486a6829cc7f09c16aa9 Mon Sep 17 00:00:00 2001 From: "noz@tinyerp.com" <> Date: Tue, 9 Mar 2010 15:06:01 +0530 Subject: [PATCH 14/19] [FIX] Correction in context syntax. bzr revid: noz@tinyerp.com-20100309093601-bdkrn8h0dxyzvzwz --- addons/account/partner_view.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/account/partner_view.xml b/addons/account/partner_view.xml index 1b4603cd8ed..0b2291224a9 100644 --- a/addons/account/partner_view.xml +++ b/addons/account/partner_view.xml @@ -91,7 +91,7 @@ - + From 46838596d42a71299d819d6dda90a6a65ffe7830 Mon Sep 17 00:00:00 2001 From: "mra (Open ERP)" Date: Tue, 9 Mar 2010 15:41:24 +0530 Subject: [PATCH 15/19] [IMP] hr_timesheet_sheet: Add group by on timesheet (Departement and user) bzr revid: mra@tinyerp.com-20100309101124-c75o9ay48gel1bma --- addons/hr_timesheet_sheet/hr_timesheet_sheet_view.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/addons/hr_timesheet_sheet/hr_timesheet_sheet_view.xml b/addons/hr_timesheet_sheet/hr_timesheet_sheet_view.xml index 17c113acd78..c2fbccca5cd 100644 --- a/addons/hr_timesheet_sheet/hr_timesheet_sheet_view.xml +++ b/addons/hr_timesheet_sheet/hr_timesheet_sheet_view.xml @@ -214,6 +214,11 @@ help="My Departments Timesheet"/> + + + + + From 236aec74aff4d9992a739e6776bf871eca652816 Mon Sep 17 00:00:00 2001 From: Julien Thewys Date: Tue, 9 Mar 2010 10:41:32 +0100 Subject: [PATCH 16/19] [IMP] Completed tests. bzr revid: jth@openerp.com-20100309094132-x4ycbziga4kqflyj --- addons/account/test/price_accuracy00.yaml | 64 ---------------- addons/account/test/price_accuracy00.yml | 92 +++++++++++++++++++++++ 2 files changed, 92 insertions(+), 64 deletions(-) delete mode 100644 addons/account/test/price_accuracy00.yaml create mode 100644 addons/account/test/price_accuracy00.yml diff --git a/addons/account/test/price_accuracy00.yaml b/addons/account/test/price_accuracy00.yaml deleted file mode 100644 index a825700e4b7..00000000000 --- a/addons/account/test/price_accuracy00.yaml +++ /dev/null @@ -1,64 +0,0 @@ -- | - Tax Scenario: - In order to check that the accounting entries and tax engine works - efficiently with all kind of price accuracies we run this test scenario. -- - Given the price accuracy for the account module is configured with two - digits -- - !record {model: decimal.precision, id: product.decimal_account}: - digits: 2 -- - And we have a tax defined -- - !record {model: account.tax, id: tax196}: - name: Tax 19.6% - amount: &tax 0.196 -- - And we define an invoice with one invoice line with a tax of *tax -- - !record {model: account.invoice, id: invoice1}: &invoice - name: Precision Test - type: out_invoice - partner_id: base.xxx - address_contact_id: - address_invoice_id: - account_id: - invoice_lines: - - product_qty: &qty 11.11 - product_uom: product.product_unit - price_unit: &price 12.34 -- - When I press on the CREATE button on the invoice to open it. -- - # to be completed -- - Then I should see an account entry linked to this invoice. -- - !record {model: account.invoice, id: invoice1}: - test: bool(move_id) -- - # This is not working, find a way to do that in YAML, *qty is not - # interpreted because it's not a node. - And this account entry must have a credit equals to 163.70€ which is - equal to *qty x *price x (1 + *tax) -- - !record {model: account.invoice, id: invoice1}: - test: abs(move_id.credit - 163.70) < 0.000001 -- - And this account entry must have a credit equals to the debit -- - !record {model: account.invoice, id: invoice1}: - test: abs(move_id.debit - move_id.credit) < 0.000001 -- - Then, I change the precision of numbers to 3 digits. -- - !record {model: decimal.precision, id: product.decimal_account}: - digits: 3 -- - And I create another invoice with the same content. -- - !record {model: account.invoice, id: invoice2}: - *invoice -- - to be completed, etc. diff --git a/addons/account/test/price_accuracy00.yml b/addons/account/test/price_accuracy00.yml new file mode 100644 index 00000000000..48b5ceca35b --- /dev/null +++ b/addons/account/test/price_accuracy00.yml @@ -0,0 +1,92 @@ +- | + Tax Scenario: + In order to check that the accounting entries and tax engine works + efficiently with all kind of price accuracies we run this test scenario. +- + Given the price accuracy for the account module is configured with two + digits +- + !record {model: decimal.precision, id: product.decimal_account}: + digits: 2 +- + And we have a tax defined +- + !record {model: account.tax, id: tax196}: + name: Tax 19.6% + amount: &tax 0.196 + account_collected_id: account.a_recv + account_paid_id: account.a_recv +- + And we define an invoice with one invoice line with a tax of *tax +- + !record {model: account.invoice, id: invoice1}: &invoice + name: Precision Test + type: out_invoice + partner_id: base.res_partner_2 + address_invoice_id: base.res_partner_address_1 + account_id: account.a_recv + date_invoice: !eval time.strftime('%Y-%m-%d') + invoice_line: + - product_id: product.product_product_pc1 + quantity: &qty 11.11 + account_id: account.a_sale + uos_id: product.product_uom_unit + price_unit: &price 12.34 + name: test + invoice_line_tax_id: + - tax196 +- + When I press on the CREATE button on the invoice to open it. +- + !python {model: account.invoice}: | + self.action_move_create(cr, uid, [ref("invoice1")]) +- + Then I should see an account entry linked to this invoice. +- + !assert {model: account.invoice, id: invoice1}: + test: + - bool(move_id) +- + # This is not working, find a way to do that in YAML, *qty is not + # interpreted because it's not a node. + And this account entry must have a credit equals to 163.70€ which is + equal to *qty x *price x (1 + *tax) +- + !assert {model: account.invoice, id: invoice1}: + test: + - abs(sum(x.credit for x in move_id.line_id) - 163.97) < 0.000001 +- + And this account entry must have a credit equals to the debit +- + !assert {model: account.invoice, id: invoice1}: + test: + - abs(sum(x.credit - x.debit for x in move_id.line_id)) < 0.000001 +- + Then, I change the precision of numbers to 3 digits. +- + !record {model: decimal.precision, id: product.decimal_account}: + digits: 3 +- + And I create another invoice with the same content. +- + !record {model: account.invoice, id: invoice2}: + *invoice +- + When I press on the CREATE button on the invoice to open it. +- + !python {model: account.invoice}: | + self.action_move_create(cr, uid, [ref("invoice2")]) +- + !python {model: account.invoice}: | + invoice = self.browse(cr, uid, ref("invoice2")) + log(sum(x.credit for x in invoice.move_id.line_id)) + assert (sum(x.credit for x in invoice.move_id.line_id) - 163.968) < 0.000001, "Pas bon !!!" +- + !assert {model: account.invoice, id: invoice2}: + test: + - abs(sum(x.credit for x in move_id.line_id) - 163.968) < 0.000001 +- + !assert {model: account.invoice, id: invoice2}: + test: + - abs(sum(x.credit - x.debit for x in move_id.line_id)) < 0.000001 + From 45ea2256a5703846a11f1d90841566d0ec277070 Mon Sep 17 00:00:00 2001 From: "mra (Open ERP)" Date: Tue, 9 Mar 2010 17:22:35 +0530 Subject: [PATCH 17/19] [IMP] project_long_term: merge improvements from rvo bzr revid: mra@tinyerp.com-20100309115235-qkv9c4uuskgraqrz --- addons/project_long_term/__terp__.py | 4 +- addons/project_long_term/project.py | 267 +++++++++--------- addons/project_long_term/project_demo.xml | 1 + addons/project_long_term/project_view.xml | 43 ++- ...hase_workflow.xml => project_workflow.xml} | 52 ++-- addons/project_long_term/wizard/__init__.py | 2 +- .../wizard/compute_phases_date.py | 129 +++++---- .../wizard/compute_tasks_date.py | 97 ++++--- .../wizard/project_resource.py | 81 ------ .../wizard/schedule_tasks.py | 92 +++--- .../wizard/working_calendar.py | 114 ++++++++ 11 files changed, 487 insertions(+), 395 deletions(-) rename addons/project_long_term/{project_phase_workflow.xml => project_workflow.xml} (71%) delete mode 100644 addons/project_long_term/wizard/project_resource.py create mode 100644 addons/project_long_term/wizard/working_calendar.py diff --git a/addons/project_long_term/__terp__.py b/addons/project_long_term/__terp__.py index 75c73396549..5367ff02567 100644 --- a/addons/project_long_term/__terp__.py +++ b/addons/project_long_term/__terp__.py @@ -26,7 +26,7 @@ "author" : "Tiny", "website" : "http://www.openerp.com", "category" : "Generic Modules/Projects & Services", - "depends" : ["project","resource"], + "depends" : ["project", "resource"], "description": """Long Term Project management module that track planning, scheduling, resources allocation. """, "init_xml" : [], @@ -35,7 +35,7 @@ #"security/ir.model.access.csv", "project_wizard.xml" , "project_view.xml", - "project_phase_workflow.xml" + "project_workflow.xml" ], 'installable': True, 'active': False, diff --git a/addons/project_long_term/project.py b/addons/project_long_term/project.py index 9c87f3048c2..595d3c82140 100644 --- a/addons/project_long_term/project.py +++ b/addons/project_long_term/project.py @@ -30,25 +30,21 @@ class project_phase(osv.osv): _name = "project.phase" _description = "Project Phase" - - def _check_recursion(self,cr,uid,ids): + def _check_recursion(self, cr, uid, ids): obj_self = self.browse(cr, uid, ids[0]) prev_ids = obj_self.previous_phase_ids next_ids = obj_self.next_phase_ids - #it should nither be in prev_ids nor in next_ids + # it should nither be in prev_ids nor in next_ids if (obj_self in prev_ids) or (obj_self in next_ids): return False ids = [id for id in prev_ids if id in next_ids] - - #both prev_ids and next_ids must be unique + # both prev_ids and next_ids must be unique if ids: return False - #unrelated project - + # unrelated project prev_ids = [rec.id for rec in prev_ids] next_ids = [rec.id for rec in next_ids] - - #iter prev_ids + # iter prev_ids while prev_ids: cr.execute('select distinct prv_phase_id from project_phase_rel where next_phase_id in ('+','.join(map(str, prev_ids))+')') prv_phase_ids = filter(None, map(lambda x: x[0], cr.fetchall())) @@ -58,8 +54,7 @@ class project_phase(osv.osv): if ids: return False prev_ids = prv_phase_ids - - #iter next_ids + # iter next_ids while next_ids: cr.execute('select distinct next_phase_id from project_phase_rel where prv_phase_id in ('+','.join(map(str, next_ids))+')') next_phase_ids = filter(None, map(lambda x: x[0], cr.fetchall())) @@ -72,21 +67,21 @@ class project_phase(osv.osv): return True def _check_dates(self, cr, uid, ids): - phase = self.read(cr, uid, ids[0],['date_start','date_end']) + phase = self.read(cr, uid, ids[0], ['date_start', 'date_end']) if phase['date_start'] and phase['date_end']: if phase['date_start'] > phase['date_end']: return False return True def _check_constraint_start(self, cr, uid, ids): - phase = self.read(cr, uid, ids[0],['date_start','constraint_date_start']) + phase = self.read(cr, uid, ids[0], ['date_start', 'constraint_date_start']) if phase['date_start'] and phase['constraint_date_start']: if phase['date_start'] < phase['constraint_date_start']: return False return True def _check_constraint_end(self, cr, uid, ids): - phase = self.read(cr, uid, ids[0],['date_end','constraint_date_end']) + phase = self.read(cr, uid, ids[0], ['date_end', 'constraint_date_end']) if phase['date_end'] and phase['constraint_date_end']: if phase['date_end'] > phase['constraint_date_end']: return False @@ -102,74 +97,76 @@ class project_phase(osv.osv): 'next_phase_ids': fields.many2many('project.phase', 'project_phase_rel', 'prv_phase_id', 'next_phase_id', 'Next Phases'), 'previous_phase_ids': fields.many2many('project.phase', 'project_phase_rel', 'next_phase_id', 'prv_phase_id', 'Previous Phases'), 'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of phases."), - 'duration': fields.float('Duration',required=True), - 'product_uom': fields.many2one('product.uom', 'Duration UoM',required=True, help="UoM (Unit of Measure) is the unit of measurement for Duration"), + 'duration': fields.float('Duration', required=True), + 'product_uom': fields.many2one('product.uom', 'Duration UoM', required=True, help="UoM (Unit of Measure) is the unit of measurement for Duration"), 'task_ids': fields.one2many('project.task', 'phase_id', "Project Tasks"), 'resource_ids': fields.one2many('project.resource.allocation', 'phase_id', "Project Resources"), 'responsible_id':fields.many2one('res.users', 'Responsible'), - 'state': fields.selection([('draft', 'Draft'),('open', 'In Progress'),('pending', 'Pending'), ('cancelled', 'Cancelled'), ('done', 'Done')], 'State', readonly=True, required=True, + 'state': fields.selection([('draft', 'Draft'), ('open', 'In Progress'), ('pending', 'Pending'), ('cancelled', 'Cancelled'), ('done', 'Done')], 'State', readonly=True, required=True, help='If the phase is created the state \'Draft\'.\n If the phase is started, the state becomes \'In Progress\'.\n If review is needed the phase is in \'Pending\' state.\ \n If the phase is over, the states is set to \'Done\'.') - } - _defaults = { 'responsible_id': lambda obj,cr,uid,context: uid, 'date_start': lambda *a: time.strftime('%Y-%m-%d'), 'state': lambda *a: 'draft', 'sequence': lambda *a: 10, } - _order = "name" _constraints = [ - (_check_recursion,'Error ! Loops In Phases Not Allowed',['next_phase_ids','previous_phase_ids']), + (_check_recursion,'Error ! Loops In Phases Not Allowed',['next_phase_ids', 'previous_phase_ids']), (_check_dates, 'Error! Phase start-date must be lower then Phase end-date.', ['date_start', 'date_end']), (_check_constraint_start, 'Error! Phase must start-after Constraint Start Date.', ['date_start', 'constraint_date_start']), (_check_constraint_end, 'Error! Phase must end-before Constraint End Date.', ['date_end', 'constraint_date_end']), - ] def onchange_project(self, cr, uid, ids, project): result = {} if project: - project_pool = self.pool.get('project.project') - project_id = project_pool.browse(cr, uid, project) + project_obj = self.pool.get('project.project') + project_id = project_obj.browse(cr, uid, project) if project_id.date_start: result['date_start'] = mx.DateTime.strptime(project_id.date_start, "%Y-%m-%d").strftime('%Y-%m-%d %H:%M:%S') - return {'value' : result} - return {'value' : {'date_start' : []}} + return {'value': result} + return {'value': {'date_start': []}} - def constraint_date_start(self, cr, uid, phase, date_end, context=None): - # Recursive call for all previous phases if change in date_start < older time - - resource_cal_pool = self.pool.get('resource.calendar') - uom_pool = self.pool.get('product.uom') - resource_pool = self.pool.get('resource.resource') + def _check_date_start(self, cr, uid, phase, date_end, context=None): + """ + Check And Compute date_end of phase if change in date_start < older time. + """ + resource_calendar_obj = self.pool.get('resource.calendar') + uom_obj = self.pool.get('product.uom') + resource_obj = self.pool.get('resource.resource') calendar_id = phase.project_id.resource_calendar_id.id - resource_id = resource_pool.search(cr, uid, [('user_id','=',phase.responsible_id.id)]) + resource_id = resource_obj.search(cr, uid, [('user_id', '=', phase.responsible_id.id)]) if resource_id: - calendar_id = resource_pool.browse(cr, uid, resource_id[0]).calendar_id.id - default_uom_id = uom_pool.search(cr, uid, [('name','=','Hour')])[0] - avg_hours = uom_pool._compute_qty(cr, uid, phase.product_uom.id, phase.duration, default_uom_id) - work_time = resource_cal_pool.interval_min_get(cr, uid, calendar_id or False, date_end, avg_hours or 0.0, resource_id or False) - dt_start = work_time[0][0].strftime('%Y-%m-%d %H:%M:%S') - self.write(cr, uid, [phase.id], {'date_start' : dt_start, 'date_end' : date_end.strftime('%Y-%m-%d %H:%M:%S')}) + cal_id = resource_obj.browse(cr, uid, resource_id[0]).calendar_id.id + if cal_id: + calendar_id = cal_id + default_uom_id = uom_obj.search(cr, uid, [('name', '=', 'Hour')])[0] + avg_hours = uom_obj._compute_qty(cr, uid, phase.product_uom.id, phase.duration, default_uom_id) + work_times = resource_calendar_obj.interval_min_get(cr, uid, calendar_id or False, date_end, avg_hours or 0.0, resource_id or False) + dt_start = work_times[0][0].strftime('%Y-%m-%d %H:%M:%S') + self.write(cr, uid, [phase.id], {'date_start': dt_start, 'date_end': date_end.strftime('%Y-%m-%d %H:%M:%S')}) - def constraint_date_end(self, cr, uid, phase, date_start, context=None): - # Recursive call for all next phases if change in date_end > older time - - resource_cal_pool = self.pool.get('resource.calendar') - uom_pool = self.pool.get('product.uom') - resource_pool = self.pool.get('resource.resource') + def _check_date_end(self, cr, uid, phase, date_start, context=None): + """ + Check And Compute date_end of phase if change in date_end > older time. + """ + resource_calendar_obj = self.pool.get('resource.calendar') + uom_obj = self.pool.get('product.uom') + resource_obj = self.pool.get('resource.resource') calendar_id = phase.project_id.resource_calendar_id.id - resource_id = resource_pool.search(cr, uid, [('user_id','=',phase.responsible_id.id)]) + resource_id = resource_obj.search(cr, uid, [('user_id', '=', phase.responsible_id.id)]) if resource_id: - calendar_id = resource_pool.browse(cr, uid, resource_id[0]).calendar_id.id - default_uom_id = uom_pool.search(cr, uid, [('name','=','Hour')])[0] - avg_hours = uom_pool._compute_qty(cr, uid, phase.product_uom.id, phase.duration, default_uom_id) - work_time = resource_cal_pool.interval_get(cr, uid, calendar_id or False, date_start, avg_hours or 0.0, resource_id or False) - dt_end = work_time[-1][1].strftime('%Y-%m-%d %H:%M:%S') - self.write(cr, uid, [phase.id], {'date_start' : date_start.strftime('%Y-%m-%d %H:%M:%S'), 'date_end' : dt_end}) + cal_id = resource_obj.browse(cr, uid, resource_id[0]).calendar_id.id + if cal_id: + calendar_id = cal_id + default_uom_id = uom_obj.search(cr, uid, [('name', '=', 'Hour')])[0] + avg_hours = uom_obj._compute_qty(cr, uid, phase.product_uom.id, phase.duration, default_uom_id) + work_times = resource_calendar_obj.interval_get(cr, uid, calendar_id or False, date_start, avg_hours or 0.0, resource_id or False) + dt_end = work_times[-1][1].strftime('%Y-%m-%d %H:%M:%S') + self.write(cr, uid, [phase.id], {'date_start': date_start.strftime('%Y-%m-%d %H:%M:%S'), 'date_end': dt_end}) def write(self, cr, uid, ids, vals, context=None): @@ -179,62 +176,62 @@ class project_phase(osv.osv): if context.get('scheduler',False): return super(project_phase, self).write(cr, uid, ids, vals, context=context) -# if the phase is performed by a resource then its calendar and efficiency also taken -# otherwise the project's working calendar considered + # Consider calendar and efficiency if the phase is performed by a resource + # otherwise consider the project's working calendar phase = self.browse(cr, uid, ids[0]) - resource_cal_pool = self.pool.get('resource.calendar') - resource_pool = self.pool.get('resource.resource') - uom_pool = self.pool.get('product.uom') + resource_calendar_obj = self.pool.get('resource.calendar') + resource_obj = self.pool.get('resource.resource') + uom_obj = self.pool.get('product.uom') resource_id = False calendar_id = phase.project_id.resource_calendar_id.id - resource_id = resource_pool.search(cr, uid, [('user_id','=',phase.responsible_id.id)]) + resource_id = resource_obj.search(cr, uid, [('user_id', '=', phase.responsible_id.id)]) if resource_id: - calendar_id = resource_pool.browse(cr, uid, resource_id[0]).calendar_id.id - default_uom_id = uom_pool.search(cr, uid, [('name','=','Hour')])[0] - avg_hours = uom_pool._compute_qty(cr, uid, phase.product_uom.id, phase.duration, default_uom_id) - -# write method changes the date_start and date_end -#for previous and next phases respectively based on valid condition + cal_id = resource_obj.browse(cr, uid, resource_id[0]).calendar_id.id + if cal_id: + calendar_id = cal_id + default_uom_id = uom_obj.search(cr, uid, [('name', '=', 'Hour')])[0] + avg_hours = uom_obj._compute_qty(cr, uid, phase.product_uom.id, phase.duration, default_uom_id) + # Change the date_start and date_end + # for previous and next phases respectively based on valid condition if vals.get('date_start'): if vals['date_start'] < phase.date_start: dt_start = mx.DateTime.strptime(vals['date_start'],'%Y-%m-%d %H:%M:%S') - work_times = resource_cal_pool.interval_get(cr, uid, calendar_id or False, dt_start, avg_hours or 0.0, resource_id or False) + work_times = resource_calendar_obj.interval_get(cr, uid, calendar_id or False, dt_start, avg_hours or 0.0, resource_id or False) vals['date_end'] = work_times[-1][1].strftime('%Y-%m-%d %H:%M:%S') super(project_phase, self).write(cr, uid, ids, vals, context=context) for prv_phase in phase.previous_phase_ids: - self.constraint_date_start(cr, uid, prv_phase, dt_start) - + self._check_date_start(cr, uid, prv_phase, dt_start) if vals.get('date_end'): if vals['date_end'] > phase.date_end: dt_end = mx.DateTime.strptime(vals['date_end'],'%Y-%m-%d %H:%M:%S') - work_times = resource_cal_pool.interval_min_get(cr, uid, calendar_id or False, dt_end, avg_hours or 0.0, resource_id or False) + work_times = resource_calendar_obj.interval_min_get(cr, uid, calendar_id or False, dt_end, avg_hours or 0.0, resource_id or False) vals['date_start'] = work_times[0][0].strftime('%Y-%m-%d %H:%M:%S') super(project_phase, self).write(cr, uid, ids, vals, context=context) for next_phase in phase.next_phase_ids: - self.constraint_date_end(cr, uid, next_phase, dt_end) + self._check_date_end(cr, uid, next_phase, dt_end) return super(project_phase, self).write(cr, uid, ids, vals, context=context) - def phase_draft(self, cr, uid, ids,*args): + def set_draft(self, cr, uid, ids, *args): self.write(cr, uid, ids, {'state': 'draft'}) return True - def phase_start(self, cr, uid, ids,*args): + def set_open(self, cr, uid, ids, *args): self.write(cr, uid, ids, {'state': 'open'}) return True - def phase_pending(self, cr, uid, ids,*args): + def set_pending(self, cr, uid, ids,*args): self.write(cr, uid, ids, {'state': 'pending'}) return True - def phase_cancel(self, cr, uid, ids,*args): + def set_cancel(self, cr, uid, ids, *args): self.write(cr, uid, ids, {'state': 'cancelled'}) return True - def phase_done(self, cr, uid, ids,*args): + def set_done(self, cr, uid, ids, *args): self.write(cr, uid, ids, {'state': 'done'}) return True @@ -246,7 +243,7 @@ class project_resource_allocation(osv.osv): _rec_name = 'resource_id' _columns = { 'resource_id': fields.many2one('resource.resource', 'Resource', required=True), - 'phase_id': fields.many2one('project.phase', 'Project Phase',required=True), + 'phase_id': fields.many2one('project.phase', 'Project Phase', required=True), 'useability': fields.float('Useability', help="Useability of this ressource for this project phase in percentage (=50%)"), } _defaults = { @@ -257,7 +254,7 @@ project_resource_allocation() class project(osv.osv): _inherit = "project.project" - + _description = "Project" _columns = { 'phase_ids': fields.one2many('project.phase', 'project_id', "Project Phases"), 'resource_calendar_id': fields.many2one('resource.calendar', 'Working Time', help="Timetable working hours to adjust the gantt diagram report"), @@ -267,7 +264,7 @@ project() class task(osv.osv): _inherit = "project.task" - + _description = "Task" _columns = { 'phase_id': fields.many2one('project.phase', 'Project Phase'), 'occupation_rate': fields.float('Occupation Rate', help='The occupation rate fields indicates how much of his time a user is working on a task. A 100% occupation rate means the user works full time on the tasks. The ending date of a task is computed like this: Starting Date + Duration / Occupation Rate.'), @@ -280,53 +277,58 @@ class task(osv.osv): def onchange_planned(self, cr, uid, ids, project, user_id=False, planned=0.0, effective=0.0, date_start=None, occupation_rate=0.0): result = {} if date_start: - resource_pool = self.pool.get('resource.resource') + resource_obj = self.pool.get('resource.resource') project_pool = self.pool.get('project.project') + hrs = float(planned / float(occupation_rate)) + calendar_id = project_pool.browse(cr, uid, project).resource_calendar_id.id resource_calendar = self.pool.get('resource.calendar') dt_start = mx.DateTime.strptime(date_start, '%Y-%m-%d %H:%M:%S') - resource_id = resource_pool.search(cr, uid, [('user_id','=',user_id)]) + resource_id = resource_obj.search(cr, uid, [('user_id','=',user_id)]) if resource_id: - resource_obj = resource_pool.browse(cr, uid, resource_id)[0] - hrs = planned / (float(occupation_rate) * resource_obj.time_efficiency) - calendar_id = resource_obj.calendar_id.id - else: - hrs = float(planned / float(occupation_rate)) - calendar_id = project_pool.browse(cr, uid, project).resource_calendar_id .id + resource = resource_obj.browse(cr, uid, resource_id)[0] + hrs = planned / (float(occupation_rate) * resource.time_efficiency) + if resource.calendar_id.id: + calendar_id = resource.calendar_id.id work_times = resource_calendar.interval_get(cr, uid, calendar_id or False, dt_start, hrs or 0.0, resource_id or False) result['date_end'] = work_times[-1][1].strftime('%Y-%m-%d %H:%M:%S') result['remaining_hours'] = planned - effective return {'value' : result} - def constraint_date_start(self, cr, uid, task, date_end, context=None): - # Recursive call for all previous tasks if change in date_start < older time - - resource_cal_pool = self.pool.get('resource.calendar') - resource_pool = self.pool.get('resource.resource') + def _check_date_start(self, cr, uid, task, date_end, context=None): + """ + Check And Compute date_end of task if change in date_start < older time. + """ + resource_calendar_obj = self.pool.get('resource.calendar') + resource_obj = self.pool.get('resource.resource') calendar_id = task.project_id.resource_calendar_id.id - hours = task.remaining_hours / task.occupation_rate - resource_id = resource_pool.search(cr, uid, [('user_id','=',task.user_id.id)]) + hours = task.planned_hours / task.occupation_rate + resource_id = resource_obj.search(cr, uid, [('user_id', '=', task.user_id.id)]) if resource_id: - resource_obj = resource_pool.browse(cr, uid, resource_id[0]) - calendar_id = resource_obj.calendar_id.id - hours = task.planned_hours / (float(task.occupation_rate) * resource_obj.time_efficiency) - work_time = resource_cal_pool.interval_min_get(cr, uid, calendar_id or False, date_end, hours or 0.0, resource_id or False) - dt_start = work_time[0][0].strftime('%Y-%m-%d %H:%M:%S') + resource = resource_obj.browse(cr, uid, resource_id[0]) + if resource.calendar_id.id: + calendar_id = resource.calendar_id.id + hours = task.planned_hours / (float(task.occupation_rate) * resource.time_efficiency) + work_times = resource_calendar_obj.interval_min_get(cr, uid, calendar_id or False, date_end, hours or 0.0, resource_id or False) + dt_start = work_times[0][0].strftime('%Y-%m-%d %H:%M:%S') self.write(cr, uid, [task.id], {'date_start' : dt_start,'date_end' : date_end.strftime('%Y-%m-%d %H:%M:%S')}) - def constraint_date_end(self, cr, uid, task, date_start, context=None): - # Recursive call for all next tasks if change in date_end > older time + def _check_date_end(self, cr, uid, task, date_start, context=None): + """ + Check And Compute date_end of task if change in date_end > older time. + """ - resource_cal_pool = self.pool.get('resource.calendar') - resource_pool = self.pool.get('resource.resource') + resource_calendar_obj = self.pool.get('resource.calendar') + resource_obj = self.pool.get('resource.resource') calendar_id = task.project_id.resource_calendar_id.id - hours = task.remaining_hours / task.occupation_rate - resource_id = resource_pool.search(cr,uid,[('user_id','=',task.user_id.id)]) + hours = task.planned_hours / task.occupation_rate + resource_id = resource_obj.search(cr,uid,[('user_id', '=', task.user_id.id)]) if resource_id: - resource_obj = resource_pool.browse(cr, uid, resource_id[0]) - calendar_id = resource_obj.calendar_id.id - hours = task.planned_hours / (float(task.occupation_rate) * resource_obj.time_efficiency) - work_time = resource_cal_pool.interval_get(cr, uid, calendar_id or False, date_start, hours or 0.0, resource_id or False) - dt_end = work_time[-1][1].strftime('%Y-%m-%d %H:%M:%S') + resource = resource_obj.browse(cr, uid, resource_id[0]) + if resource.calendar_id.id: + calendar_id = resource.calendar_id.id + hours = task.planned_hours / (float(task.occupation_rate) * resource.time_efficiency) + work_times = resource_calendar_obj.interval_get(cr, uid, calendar_id or False, date_start, hours or 0.0, resource_id or False) + dt_end = work_times[-1][1].strftime('%Y-%m-%d %H:%M:%S') self.write(cr, uid, [task.id], {'date_start' : date_start.strftime('%Y-%m-%d %H:%M:%S'),'date_end' : dt_end}) def write(self, cr, uid, ids, vals, context=None): @@ -337,44 +339,43 @@ class task(osv.osv): if context.get('scheduler',False): return super(task, self).write(cr, uid, ids, vals, context=context) -# if the task is performed by a resource then its calendar and efficiency also taken -# otherwise the project's working calendar considered + # Consider calendar and efficiency if the task is performed by a resource + # otherwise consider the project's working calendar task_id = ids if isinstance(ids, list): task_id = ids[0] - tasks = self.browse(cr, uid, task_id, context=context) - resource_cal_pool = self.pool.get('resource.calendar') - resource_pool = self.pool.get('resource.resource') - calendar_id = tasks.project_id.resource_calendar_id.id - hrs = tasks.remaining_hours / tasks.occupation_rate - resource_id = resource_pool.search(cr, uid, [('user_id','=',tasks.user_id.id)]) + task_rec = self.browse(cr, uid, task_id, context=context) + resource_calendar_obj = self.pool.get('resource.calendar') + resource_obj = self.pool.get('resource.resource') + calendar_id = task_rec.project_id.resource_calendar_id.id + hrs = task_rec.planned_hours / task_rec.occupation_rate + resource_id = resource_obj.search(cr, uid, [('user_id', '=', task_rec.user_id.id)]) if resource_id: - resource_obj = resource_pool.browse(cr, uid, resource_id[0]) - calendar_id = resource_obj.calendar_id.id - hrs = tasks.planned_hours / (float(tasks.occupation_rate) * resource_obj.time_efficiency) + resource = resource_obj.browse(cr, uid, resource_id[0]) + if resource.calendar_id.id: + calendar_id = resource.calendar_id.id + hrs = task_rec.planned_hours / (float(task_rec.occupation_rate) * resource.time_efficiency) -# write method changes the date_start and date_end -# for previous and next tasks respectively based on valid condition + # Change the date_start and date_end + # for previous and next tasks respectively based on valid condition if vals.get('date_start'): - if vals['date_start'] < tasks.date_start: - dt_start = mx.DateTime.strptime(vals['date_start'],'%Y-%m-%d %H:%M:%S') - work_times = resource_cal_pool.interval_get(cr, uid, calendar_id or False, dt_start, hrs or 0.0, resource_id or False) + if vals['date_start'] < task_rec.date_start: + dt_start = mx.DateTime.strptime(vals['date_start'], '%Y-%m-%d %H:%M:%S') + work_times = resource_calendar_obj.interval_get(cr, uid, calendar_id or False, dt_start, hrs or 0.0, resource_id or False) vals['date_end'] = work_times[-1][1].strftime('%Y-%m-%d %H:%M:%S') super(task, self).write(cr, uid, ids, vals, context=context) - for prv_task in tasks.parent_ids: - self.constraint_date_start(cr, uid, prv_task, dt_start) - + for prv_task in task_rec.parent_ids: + self._check_date_start(cr, uid, prv_task, dt_start) if vals.get('date_end'): - if vals['date_end'] > tasks.date_end: - dt_end = mx.DateTime.strptime(vals['date_end'],'%Y-%m-%d %H:%M:%S') - hrs = tasks.remaining_hours / tasks.occupation_rate - work_times = resource_cal_pool.interval_min_get(cr, uid, calendar_id or False, dt_end, hrs or 0.0, resource_id or False) + if vals['date_end'] > task_rec.date_end: + dt_end = mx.DateTime.strptime(vals['date_end'], '%Y-%m-%d %H:%M:%S') + work_times = resource_calendar_obj.interval_min_get(cr, uid, calendar_id or False, dt_end, hrs or 0.0, resource_id or False) vals['date_start'] = work_times[0][0].strftime('%Y-%m-%d %H:%M:%S') super(task, self).write(cr, uid, ids, vals, context=context) - for next_task in tasks.child_ids: - self.constraint_date_end(cr, uid, next_task, dt_end) + for next_task in task_rec.child_ids: + self._check_date_end(cr, uid, next_task, dt_end) return super(task, self).write(cr, uid, ids, vals, context=context) diff --git a/addons/project_long_term/project_demo.xml b/addons/project_long_term/project_demo.xml index 64dcb19c31e..24bc7ff9438 100644 --- a/addons/project_long_term/project_demo.xml +++ b/addons/project_long_term/project_demo.xml @@ -201,6 +201,7 @@ + diff --git a/addons/project_long_term/project_view.xml b/addons/project_long_term/project_view.xml index 059e0a8300a..0af1e09c422 100644 --- a/addons/project_long_term/project_view.xml +++ b/addons/project_long_term/project_view.xml @@ -1,6 +1,11 @@ + + # ------------------------------------------------------ + # Project Resource Allocation + # ------------------------------------------------------ + project.resource.allocation.form project.resource.allocation @@ -13,7 +18,7 @@ - + project.resource.allocation.list project.resource.allocation @@ -26,7 +31,11 @@
- + + # ------------------------------------------------------ + # Project Phase + # ------------------------------------------------------ + project.phase.form project.phase @@ -57,11 +66,11 @@
-