diff --git a/addons/account/account.py b/addons/account/account.py index c5efb062570..aa0a27ad76d 100644 --- a/addons/account/account.py +++ b/addons/account/account.py @@ -1601,8 +1601,8 @@ class account_tax(osv.osv): # # Fields used for the VAT declaration # - 'base_code_id': fields.many2one('account.tax.code', 'Base Code', help="Use this code for the VAT declaration."), - 'tax_code_id': fields.many2one('account.tax.code', 'Tax Code', help="Use this code for the VAT declaration."), + 'base_code_id': fields.many2one('account.tax.code', 'Account Base Code', help="Use this code for the VAT declaration."), + 'tax_code_id': fields.many2one('account.tax.code', 'Account Tax Code', help="Use this code for the VAT declaration."), 'base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."), 'tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."), diff --git a/addons/account/report/account_general_journal.rml b/addons/account/report/account_general_journal.rml index d03aca63968..77f750196d4 100644 --- a/addons/account/report/account_general_journal.rml +++ b/addons/account/report/account_general_journal.rml @@ -345,6 +345,8 @@ + + [[lines(o.id) and removeParentNode('para') or removeParentNode('section') ]] [[ display_currency(data) == False or removeParentNode('blockTable') ]] @@ -364,7 +366,10 @@ + + [[ display_currency(data) or removeParentNode('blockTable') ]] + [[lines(o.id) and removeParentNode('para') or removeParentNode('section') ]] [[ repeatIn(lines(o.id),'line')]] [[ line['code'] ]] [[ line['name'] ]] @@ -374,6 +379,7 @@ [[ line['currency_id'] and formatLang(line['amount_currency'] ) ]] [[ line['currency_code'] or '' ]] + diff --git a/addons/account/report/account_partner_balance.rml b/addons/account/report/account_partner_balance.rml index e77f62c9fb4..fba666fe319 100644 --- a/addons/account/report/account_partner_balance.rml +++ b/addons/account/report/account_partner_balance.rml @@ -191,7 +191,7 @@ - Chat Account + Chart of Account Fiscal Year Journals Filter By [[ get_filter(data)!='No Filter' and get_filter(data) ]] diff --git a/addons/account/wizard/account_report_common_view.xml b/addons/account/wizard/account_report_common_view.xml index 3779a03a7be..0717e3cafb1 100644 --- a/addons/account/wizard/account_report_common_view.xml +++ b/addons/account/wizard/account_report_common_view.xml @@ -8,7 +8,7 @@ form - + diff --git a/addons/project/company.py b/addons/project/company.py index f0e66e468b4..ea9458bb468 100644 --- a/addons/project/company.py +++ b/addons/project/company.py @@ -20,6 +20,7 @@ ############################################################################## from osv import fields, osv +from tools.translate import _ class res_company(osv.osv): _inherit = 'res.company' @@ -30,6 +31,12 @@ class res_company(osv.osv): "forget to setup the right unit of measure in your employees.", ), } + + def write(self, cr, uid, ids,vals, context={}): + task_ids=self.pool.get('project.task').search(cr, uid, [('state','in',['open', 'pending'])]) + if ('project_time_mode_id' in vals) and task_ids: + raise osv.except_osv(_('Error !'), _('You can not modify Project Time Unit as there are open or pending tasks created with current time unit.)) + return super(res_company,self).write(cr, uid, ids, vals, context=context) res_company() diff --git a/addons/project/project.py b/addons/project/project.py index 603154d28fe..559b5f31961 100644 --- a/addons/project/project.py +++ b/addons/project/project.py @@ -92,7 +92,26 @@ where prp.id in %s''',(tuple(ids),)) child_ids = self.get_all_child_projects(cr, uid, child_ids) return ids+child_ids - + + def _get_user_and_default_uom_ids(self, cr, uid): + users_obj = self.pool.get('res.users') + model_data_obj = self.pool.get('ir.model.data') + + model_data_id = model_data_obj._get_id(cr, uid, 'product', 'uom_hour') + default_uom = user_uom = model_data_obj.read(cr, uid, [model_data_id], ['res_id'])[0]['res_id'] + obj_tm = users_obj.browse(cr, uid, uid).company_id.project_time_mode_id + if obj_tm: + user_uom = obj_tm.id + return user_uom, default_uom + + def _compute_duration(self, cr, uid, duration=0.0, to_uom='default'): + uom_obj = self.pool.get('product.uom') + user_uom, default_uom = self._get_user_and_default_uom_ids(cr, uid) + if to_uom == 'default': + return uom_obj._compute_qty(cr, uid, user_uom, duration, default_uom) + else: + return uom_obj._compute_qty(cr, uid, default_uom, duration, user_uom) + def _progress_rate(self, cr, uid, ids, names, arg, context=None): res = {}.fromkeys(ids, 0.0) progress = {} @@ -127,11 +146,18 @@ where prp.id in %s''',(tuple(ids),)) p = tocompute.pop() for i in range(3): s[i] += progress.get(p, (0.0, 0.0, 0.0))[i] + + user_uom, def_uom = self._get_user_and_default_uom_ids(cr, uid) + if user_uom != def_uom: + s[0] = self._compute_duration(cr, uid, s[0]) + s[1] = self._compute_duration(cr, uid, s[1]) + s[2] = self._compute_duration(cr, uid, s[2]) + if project.state == 'close': progress_rate = 100.0 else: progress_rate = s[1] and round(min(100.0 * s[2] / s[1], 99.99), 2) - + res[project.id] = { 'planned_hours': s[0], 'effective_hours': s[2], @@ -145,7 +171,7 @@ where prp.id in %s''',(tuple(ids),)) if proj.tasks: raise osv.except_osv(_('Operation Not Permitted !'), _('You can not delete a project with tasks. I suggest you to deactivate it.')) return super(project, self).unlink(cr, uid, ids, *args, **kwargs) - + _columns = { 'complete_name': fields.function(_complete_name, method=True, string="Project Name", type='char', size=250), 'active': fields.boolean('Active', help="If the active field is set to true, it will allow you to hide the project without removing it."), @@ -234,11 +260,10 @@ where prp.id in %s''',(tuple(ids),)) default['name'] = proj.name+_(' (copy)') res = super(project, self).copy(cr, uid, id, default, context) - task_ids = task_obj.search(cr, uid, [('project_id','=', res), ('active','=',False)]) + task_ids = task_obj.search(cr, uid, [('project_id','=', res), ('active','=',True)]) tasks = task_obj.browse(cr, uid, task_ids) for task in tasks: date_deadline = None - date_end = None if task.date_start: ds = date(*time.strptime(task.date_start,'%Y-%m-%d %H:%M:%S')[:3]) if task.date_deadline: @@ -246,11 +271,11 @@ where prp.id in %s''',(tuple(ids),)) diff = dd-ds date_deadline = (datetime.now()+diff).strftime('%Y-%m-%d %H:%M:%S') task_obj.write(cr, uid, task.id, {'active':True, - 'date_start':time.strftime('%Y-%m-%d %H:%M:%S'), + 'date_start': False, + 'date_end': False, 'date_deadline':date_deadline, - 'date_end':date_end}) + }) - ids = self.search(cr, uid, [('parent_id','child_of', [res])]) return res def duplicate_template(self, cr, uid, ids, context=None): @@ -317,29 +342,46 @@ class task(osv.osv): _description = "Task" _log_create = True _date_name = "date_start" + + def search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False): + for domain in args: + if len(domain) == 3 and domain[0] == 'project_id': + id = isinstance(domain[2], list) and int(domain[2][0]) or int(domain[2]) + if id: + if self.pool.get('project.project').read(cr, user, id, ['state'])['state'] == 'template': + args.append(['active', '=', False]) + return super(task, self).search(cr, user, args, offset=offset, limit=limit, order=order, context=context, count=count) def _str_get(self, task, level=0, border='***', context=None): return border+' '+(task.user_id and task.user_id.name.upper() or '')+(level and (': L'+str(level)) or '')+(' - %.1fh / %.1fh'%(task.effective_hours or 0.0,task.planned_hours))+' '+border+'\n'+ \ border[0]+' '+(task.name or '')+'\n'+ \ (task.description or '')+'\n\n' - + # Compute: effective_hours, total_hours, progress def _hours_get(self, cr, uid, ids, field_names, args, context=None): + project_obj = self.pool.get('project.project') res = {} cr.execute("SELECT task_id, COALESCE(SUM(hours),0) FROM project_task_work WHERE task_id IN %s GROUP BY task_id",(tuple(ids),)) hours = dict(cr.fetchall()) + + user_uom, default_uom = project_obj._get_user_and_default_uom_ids(cr, uid) + if user_uom != default_uom: + for task in self.browse(cr, uid, ids, context=context): + if hours.get(task.id, False): + dur_in_user_uom = project_obj._compute_duration(cr, uid, duration=hours.get(task.id, 0.0), to_uom='user') + hours[task.id] = dur_in_user_uom + for task in self.browse(cr, uid, ids, context=context): res[task.id] = {'effective_hours': hours.get(task.id, 0.0), 'total_hours': task.remaining_hours + hours.get(task.id, 0.0)} + res[task.id]['delay_hours'] = res[task.id]['total_hours'] - task.planned_hours res[task.id]['progress'] = 0.0 if (task.remaining_hours + hours.get(task.id, 0.0)): - if task.state != 'done': - res[task.id]['progress'] = round(min(100.0 * hours.get(task.id, 0.0) / res[task.id]['total_hours'], 99.99), 2) - - if task.state in ('done', 'cancel'): + res[task.id]['progress'] = round(min(100.0 * hours.get(task.id, 0.0) / res[task.id]['total_hours'], 99.99),2) + if task.state in ('done','cancelled'): res[task.id]['progress'] = 100.0 - res[task.id]['delay_hours'] = res[task.id]['total_hours'] - task.planned_hours return res + def onchange_planned(self, cr, uid, ids, planned = 0.0, effective = 0.0): return {'value':{'remaining_hours': planned - effective}} @@ -357,6 +399,7 @@ class task(osv.osv): def copy_data(self, cr, uid, id, default={}, context=None): default = default or {} default['work_ids'] = [] + default['remaining_hours'] = float(self.read(cr, uid, id, ['planned_hours'])['planned_hours']) default['active'] = True return super(task, self).copy_data(cr, uid, id, default, context) @@ -375,7 +418,7 @@ class task(osv.osv): if task.project_id.active == False or task.project_id.state == 'template': res[task.id] = False return res - + _columns = { 'active': fields.function(_is_template, method=True, store=True, string='Not a Template Task', type='boolean', help="This field is computed automatically and have the same behavior than the boolean 'active' field: if the task is linked to a template or unactivated project, it will be hidden unless specifically asked."), 'name': fields.char('Task Summary', size=128, required=True), @@ -397,7 +440,7 @@ class task(osv.osv): 'notes': fields.text('Notes'), 'planned_hours': fields.float('Planned Hours', required=True, help='Estimated time to do the task, usually set by the project manager when the task is in draft state.'), 'effective_hours': fields.function(_hours_get, method=True, string='Hours Spent', multi='hours', store=True, help="Computed using the sum of the task work done."), - 'remaining_hours': fields.float('Remaining Hours', digits=(16,4), help="Total remaining time, can be re-estimated periodically by the assignee of the task."), + 'remaining_hours': fields.float('Remaining Hours', digits=(16,2), help="Total remaining time, can be re-estimated periodically by the assignee of the task."), 'total_hours': fields.function(_hours_get, method=True, string='Total Hours', multi='hours', store=True, help="Computed as: Time Spent + Remaining Time."), 'progress': fields.function(_hours_get, method=True, string='Progress (%)', multi='hours', store=True, help="Computed as: Time Spent / Total Time."), 'delay_hours': fields.function(_hours_get, method=True, string='Delay Hours', multi='hours', store=True, help="Computed as difference of the time estimated by the project manager and the real time to close the task."), @@ -614,27 +657,55 @@ class project_work(osv.osv): } _order = "date desc" - + def create(self, cr, uid, vals, *args, **kwargs): - if 'hours' in vals and (not vals['hours']): + if (not 'task_id' in vals) or (not 'hours' in vals): + return super(project_work,self).create(cr, uid, vals, *args, **kwargs) + if not vals['hours']: vals['hours'] = 0.00 - if 'task_id' in vals: - cr.execute('update project_task set remaining_hours=remaining_hours - %s where id=%s', (vals.get('hours',0.0), vals['task_id'])) + + project_obj = self.pool.get('project.project') + user_uom, default_uom = project_obj._get_user_and_default_uom_ids(cr, uid) + duration = vals['hours'] + + if user_uom != default_uom: + duration = project_obj._compute_duration(cr, uid, duration=duration, to_uom='user') + cr.execute('update project_task set remaining_hours=remaining_hours - %s where id=%s', (duration, vals['task_id'])) return super(project_work,self).create(cr, uid, vals, *args, **kwargs) def write(self, cr, uid, ids, vals, context=None): if context is None: context = {} - if 'hours' in vals and (not vals['hours']): - vals['hours'] = 0.00 - if 'hours' in vals: + if not vals['hours']: + vals['hours'] = 0.00 + + project_obj = self.pool.get('project.project') + user_uom, default_uom = project_obj._get_user_and_default_uom_ids(cr, uid) + duration = vals['hours'] + + if user_uom == default_uom: for work in self.browse(cr, uid, ids, context): - cr.execute('update project_task set remaining_hours=remaining_hours - %s + (%s) where id=%s', (vals.get('hours',0.0), work.hours, work.task_id.id)) + cr.execute('update project_task set remaining_hours=remaining_hours - %s + (%s) where id=%s', (duration, work.hours, work.task_id.id)) + else: + for work in self.browse(cr, uid, ids, context): + duration = project_obj._compute_duration(cr, uid, duration=duration, to_uom='user') + del_work = project_obj._compute_duration(cr, uid, duration=work.hours, to_uom='user') + cr.execute('update project_task set remaining_hours=remaining_hours - %s + (%s) where id=%s', (duration, del_work, work.task_id.id)) + return super(project_work,self).write(cr, uid, ids, vals, context) def unlink(self, cr, uid, ids, *args, **kwargs): - for work in self.browse(cr, uid, ids): - cr.execute('update project_task set remaining_hours=remaining_hours + %s where id=%s', (work.hours, work.task_id.id)) + context = kwargs.get('context', {}) + project_obj = self.pool.get('project.project') + user_uom, default_uom = project_obj._get_user_and_default_uom_ids(cr, uid) + + if user_uom == default_uom: + for work in self.browse(cr, uid, ids, context): + cr.execute('update project_task set remaining_hours=remaining_hours + %s where id=%s', (work.hours, work.task_id.id)) + else: + for work in self.browse(cr, uid, ids, context): + duration = project_obj._compute_duration(cr, uid, duration=work.hours, to_uom='user') + cr.execute('update project_task set remaining_hours=remaining_hours + %s where id=%s', (duration, work.task_id.id)) return super(project_work,self).unlink(cr, uid, ids, *args, **kwargs) project_work() diff --git a/addons/project/project_view.xml b/addons/project/project_view.xml index 166e64217ac..d9b78e16e72 100644 --- a/addons/project/project_view.xml +++ b/addons/project/project_view.xml @@ -424,11 +424,6 @@ groups="base.group_extended"/> - @@ -460,7 +455,7 @@ tree,form,calendar,gantt,graph - {"search_default_project_id":project_id, "search_default_user_id":uid, 'search_default_current': 1} + {"search_default_project_id":project_id, "search_default_user_id":uid, "search_default_current": 1} In OpenERP, a project is made of a set of activities (or tasks) for completion.A list view allows the manager to quickly check for task status and make it evolve, delegate task, etc. A search tool allows a multi-criteria sort of activities. diff --git a/addons/project_gtd/project_gtd.py b/addons/project_gtd/project_gtd.py index 0549dc0bedf..756aa77bc3f 100644 --- a/addons/project_gtd/project_gtd.py +++ b/addons/project_gtd/project_gtd.py @@ -43,7 +43,6 @@ class project_gtd_context(osv.osv): _columns = { 'name': fields.char('Context', size=64, required=True, select=1), 'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of contexts."), - 'project_default_id': fields.many2one('project.project', 'Default Project', required=True), } _defaults = { 'sequence': 1 diff --git a/addons/project_gtd/project_gtd_demo.xml b/addons/project_gtd/project_gtd_demo.xml index a5bf075b6f6..0f2a4d0b06c 100644 --- a/addons/project_gtd/project_gtd_demo.xml +++ b/addons/project_gtd/project_gtd_demo.xml @@ -5,22 +5,18 @@ Office 0 - Travel 2 - Car 1 - Home 3 - diff --git a/addons/project_gtd/test/gtd_test.yml b/addons/project_gtd/test/gtd_test.yml index 84422af3467..5c05dfd76cc 100644 --- a/addons/project_gtd/test/gtd_test.yml +++ b/addons/project_gtd/test/gtd_test.yml @@ -2,8 +2,6 @@ Create a task 'Develop time management module' with monthly timebox - !record {model: project.task, id: project_task_developtimemanagementmodule0}: - date_end: '2010-06-04 10:00:00' - date_start: '2010-06-02 14:42:21' name: Develop time management module planned_hours: 15.0 project_id: project.project_project_22 diff --git a/addons/project_issue_sheet/project_issue_sheet.py b/addons/project_issue_sheet/project_issue_sheet.py index f5e7d776436..2d948afa1fb 100644 --- a/addons/project_issue_sheet/project_issue_sheet.py +++ b/addons/project_issue_sheet/project_issue_sheet.py @@ -27,7 +27,7 @@ class project_issue(osv.osv): _description = 'project issue' _columns = { 'timesheet_ids': fields.one2many('hr.analytic.timesheet', 'issue_id', 'Timesheets'), - 'analytic_account_id': fields.related('project_id', 'analytic_account_id', string='Analytic Account') + 'analytic_account_id': fields.related('project_id', 'analytic_account_id', type='many2one', relation='account.analytic.account',string='Analytic Account') } project_issue() diff --git a/addons/project_long_term/project_long_term_view.xml b/addons/project_long_term/project_long_term_view.xml index 7d51f79530a..3c6c19a4d64 100644 --- a/addons/project_long_term/project_long_term_view.xml +++ b/addons/project_long_term/project_long_term_view.xml @@ -133,7 +133,7 @@ - + diff --git a/addons/project_long_term/test/schedule_project_phases.yml b/addons/project_long_term/test/schedule_project_phases.yml index 7f59b476c65..66bca5ff97d 100644 --- a/addons/project_long_term/test/schedule_project_phases.yml +++ b/addons/project_long_term/test/schedule_project_phases.yml @@ -136,8 +136,8 @@ - !python {model: project.project}: | prj=self.browse(cr, uid, [ref("project_project_worldbanksproject0")])[0] - for task in prj.tasks: - if (not task.user_id) or (not task.date_start) or (not task.date_end): + for phase in prj.phase_ids: + if (not phase.responsible_id) or (not phase.date_start) or (not phase.date_end): raise AssertionError("Tasks not scheduled") \ No newline at end of file diff --git a/addons/project_long_term/wizard/project_compute_phases.py b/addons/project_long_term/wizard/project_compute_phases.py index e13d307aab2..0c8e11aa9dd 100644 --- a/addons/project_long_term/wizard/project_compute_phases.py +++ b/addons/project_long_term/wizard/project_compute_phases.py @@ -74,6 +74,8 @@ class project_compute_phases(osv.osv_memory): resource = resource_obj.browse(cr, uid, resource_id, context=context)[0] time_efficiency = resource.time_efficiency leaves = wkcal.compute_leaves(cr, uid, calendar_id , resource.id, resource.calendar_id.id) + if not phase.responsible_id: + raise osv.except_osv(_('No responsible person assigned !'),_("You must assign a responsible person for phase '%s' !") % (phase.name,)) phase_resource_obj = classobj((phase.responsible_id.name.encode('utf8')), (Resource,), {'__doc__': phase.responsible_id.name, '__name__': phase.responsible_id.name, diff --git a/addons/project_planning/project_planning_report.xml b/addons/project_planning/project_planning_report.xml index 5532adaecbf..2b71602f97c 100644 --- a/addons/project_planning/project_planning_report.xml +++ b/addons/project_planning/project_planning_report.xml @@ -3,4 +3,4 @@ - + \ No newline at end of file diff --git a/addons/project_retro_planning/__openerp__.py b/addons/project_retro_planning/__openerp__.py index a9ba0a1111a..e906e587a7b 100644 --- a/addons/project_retro_planning/__openerp__.py +++ b/addons/project_retro_planning/__openerp__.py @@ -24,8 +24,8 @@ 'version': '0.1', 'category': 'Generic Modules/Projects & Services', 'description': """ - - If deadline date of project is changed - then the deadline date for all the tasks will change to the same """, + - If end date of project is changed + then the deadline date and start date for all the tasks will change accordingly """, 'author': 'OpenERP SA', 'depends': ['base', 'project'], 'init_xml': [], diff --git a/addons/project_retro_planning/project_retro_planning.py b/addons/project_retro_planning/project_retro_planning.py index ead6915326e..6360b3cae13 100644 --- a/addons/project_retro_planning/project_retro_planning.py +++ b/addons/project_retro_planning/project_retro_planning.py @@ -35,12 +35,13 @@ class project_project(osv.osv): if prj.date: old_end_date = date(*time.strptime(prj.date,'%Y-%m-%d')[:3]) for task in prj.tasks: - start_dt = (datetime(*time.strptime(task.date_start,'%Y-%m-%d %H:%M:%S')[:6])+(new_end_date-old_end_date)).strftime('%Y-%m-%d %H:%M:%S') - if task.date_deadline: - deadline_dt = (datetime(*time.strptime(task.date_deadline,'%Y-%m-%d')[:6])+(new_end_date-old_end_date)).strftime('%Y-%m-%d %H:%M:%S') - self.pool.get('project.task').write(cr, uid, task.id, {'date_start':start_dt, 'date_deadline':deadline_dt}) - else: - self.pool.get('project.task').write(cr, uid, task.id, {'date_start':start_dt}) + if task.date_start: + start_dt = (date(*time.strptime(str(task.date_start),'%Y-%m-%d %H:%M:%S')[:3])+(new_end_date-old_end_date)).strftime('%Y-%m-%d %H:%M:%S') + if task.date_deadline: + deadline_dt = (datetime(*time.strptime(str(task.date_deadline),'%Y-%m-%d')[:3])+(new_end_date-old_end_date)).strftime('%Y-%m-%d') + self.pool.get('project.task').write(cr, uid, task.id, {'date_start':start_dt, 'date_deadline':deadline_dt}) + else: + self.pool.get('project.task').write(cr, uid, task.id, {'date_start':start_dt}) return super(project_project,self).write(cr, uid, ids, vals, *args, **kwargs) project_project()