+
+
+
+
+
From e70605f2d19c70e108207ca702b616afe7e92e8c Mon Sep 17 00:00:00 2001
From: vbh-odoo
Date: Tue, 28 Jul 2015 18:18:08 +0530
Subject: [PATCH 14/20] [MIG] project_issue: migration to new api
---
addons/project_issue/__init__.py | 2 +-
addons/project_issue/__openerp__.py | 14 +-
.../data/mail_message_subtype_data.xml | 9 +-
.../project_issue/data/project_issue_demo.xml | 94 ++-
addons/project_issue/models/__init__.py | 2 +-
.../models/account_analytic_account.py | 41 +-
addons/project_issue/models/project_issue.py | 555 ++++++++----------
.../project_issue/models/project_project.py | 77 +--
.../project_issue/models/project_task_type.py | 4 +-
addons/project_issue/models/res_partner.py | 21 +-
.../report/project_issue_report.py | 49 +-
.../report/project_issue_report_view.xml | 10 +-
.../security/project_issue_security.xml | 8 +-
.../views/account_analytic_account_view.xml | 32 +-
.../project_issue/views/project_dashboard.xml | 1 +
.../views/project_issue_menu.xml | 25 +-
.../views/project_issue_view.xml | 61 +-
.../views/project_project_view.xml | 62 +-
.../project_issue/views/res_partner_view.xml | 45 +-
19 files changed, 469 insertions(+), 643 deletions(-)
diff --git a/addons/project_issue/__init__.py b/addons/project_issue/__init__.py
index 54bcaa9cd53..a5f67aebd79 100644
--- a/addons/project_issue/__init__.py
+++ b/addons/project_issue/__init__.py
@@ -1,4 +1,4 @@
-# -*- encoding: utf-8 -*-
+# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import models
diff --git a/addons/project_issue/__openerp__.py b/addons/project_issue/__openerp__.py
index 7637a61e7ce..d003b39a9dd 100644
--- a/addons/project_issue/__openerp__.py
+++ b/addons/project_issue/__openerp__.py
@@ -1,4 +1,4 @@
-# -*- encoding: utf-8 -*-
+# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
{
@@ -19,18 +19,18 @@ It allows the manager to quickly check the issues, assign them and decide on the
'project',
],
'data': [
+ 'data/mail_message_subtype_data.xml',
+ 'report/project_issue_report_view.xml',
+ 'security/project_issue_security.xml',
+ 'security/ir.model.access.csv',
'views/project_issue_view.xml',
'views/project_issue_config_settings_views.xml',
'views/account_analytic_account_view.xml',
'views/project_project_view.xml',
'views/res_partner_view.xml',
'views/project_issue_menu.xml',
- 'report/project_issue_report_view.xml',
- 'security/project_issue_security.xml',
- 'security/ir.model.access.csv',
- 'data/mail_message_subtype_data.xml',
'views/project_dashboard.xml',
- ],
+ ],
'demo': ['data/project_issue_demo.xml'],
'test': [
'test/issue_users.yml',
@@ -38,7 +38,5 @@ It allows the manager to quickly check the issues, assign them and decide on the
'test/issue_process.yml',
'test/issue_demo.yml'
],
- 'installable': True,
- 'auto_install': False,
'application': True,
}
diff --git a/addons/project_issue/data/mail_message_subtype_data.xml b/addons/project_issue/data/mail_message_subtype_data.xml
index 4c425ff2104..2386d408295 100644
--- a/addons/project_issue/data/mail_message_subtype_data.xml
+++ b/addons/project_issue/data/mail_message_subtype_data.xml
@@ -1,9 +1,9 @@
-
-
+
+
-
+ mail.channelnotification
@@ -73,6 +73,5 @@ Access all issues from the top Project menu, and access the issues of a specific
project_id
-
-
+
diff --git a/addons/project_issue/data/project_issue_demo.xml b/addons/project_issue/data/project_issue_demo.xml
index 09f80d2d493..b2431710833 100644
--- a/addons/project_issue/data/project_issue_demo.xml
+++ b/addons/project_issue/data/project_issue_demo.xml
@@ -1,7 +1,5 @@
-
-
-
-
+
+
@@ -21,7 +19,6 @@
-
@@ -37,13 +34,11 @@
-
- 0
+
-
-
+ Bug in Accounts moduleagr@agrolait.com
@@ -52,23 +47,20 @@
-
+ 1
-
-
+ Program not giving proper output
-
- 0
+
-
-
+ Output incorrect
@@ -78,36 +70,33 @@
-
+ 1
-
-
+ Problem loading page
-
+ 1
-
-
+ Page not Found
-
+ 1
-
-
+ Programming Error
@@ -117,72 +106,65 @@
-
+ 2
-
-
+ Logical Error in Program
-
+ 2
-
-
+ Constraint Error
-
+ 2
-
-
+ Error in Program
-
+ 2
-
-
+ Patches Error in Program
-
- 0
+
-
-
+ New Features To Be Added
-
+ 1
-
-
+ Add menus to the moduleinfo@opensides.be
@@ -190,12 +172,11 @@
-
+ 1
-
-
+ Include Attendance sheet in Projectcontact@tecsas.fr
@@ -203,44 +184,41 @@
-
+ 1
-
-
+ Create new object
-
- 0
+
-
-
+ Improve Reports in HRMS
-
+ 2
-
-
+ Improve Reports in PMS
+
-
+
diff --git a/addons/project_issue/models/__init__.py b/addons/project_issue/models/__init__.py
index 859bb01a7b3..96cee86e356 100644
--- a/addons/project_issue/models/__init__.py
+++ b/addons/project_issue/models/__init__.py
@@ -1,4 +1,4 @@
-# -*- encoding: utf-8 -*-
+# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import project_issue
diff --git a/addons/project_issue/models/account_analytic_account.py b/addons/project_issue/models/account_analytic_account.py
index 3dc67c26f6f..e6de19fe2b7 100644
--- a/addons/project_issue/models/account_analytic_account.py
+++ b/addons/project_issue/models/account_analytic_account.py
@@ -1,35 +1,30 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-from openerp.osv import fields, osv
-from openerp.tools.translate import _
-from openerp.exceptions import UserError
+from odoo import api, fields, models, _
+from odoo.exceptions import UserError
-class account_analytic_account(osv.Model):
+class AccountAnalyticAccount(models.Model):
_inherit = 'account.analytic.account'
- _description = 'Analytic Account'
- _columns = {
- 'use_issues': fields.boolean('Use Issues', help="Check this box to manage customer activities through this project"),
- }
+ use_issues = fields.Boolean(string='Use Issues', help="Check this box to manage customer activities through this project")
- def on_change_template(self, cr, uid, ids, template_id, date_start=False, context=None):
- res = super(account_analytic_account, self).on_change_template(cr, uid, ids, template_id, date_start=date_start, context=context)
+ @api.multi
+ def on_change_template(self, template_id, date_start=False):
+ res = super(AccountAnalyticAccount, self).on_change_template(template_id, date_start=date_start)
if template_id and 'value' in res:
- template = self.browse(cr, uid, template_id, context=context)
- res['value']['use_issues'] = template.use_issues
+ account_analytic_account = self.browse(template_id)
+ res['value']['use_issues'] = account_analytic_account.use_issues
return res
- def _trigger_project_creation(self, cr, uid, vals, context=None):
- if context is None:
- context = {}
- res = super(account_analytic_account, self)._trigger_project_creation(cr, uid, vals, context=context)
- return res or (vals.get('use_issues') and not 'project_creation_in_progress' in context)
-
- def unlink(self, cr, uid, ids, context=None):
- proj_ids = self.pool['project.project'].search(cr, uid, [('analytic_account_id', 'in', ids)])
- has_issues = self.pool['project.issue'].search(cr, uid, [('project_id', 'in', proj_ids)], count=True, context=context)
- if has_issues:
+ @api.multi
+ def unlink(self):
+ if self.env['project.issue'].search_count([('project_id.analytic_account_id', 'in', self.ids)]):
raise UserError(_('Please remove existing issues in the project linked to the accounts you want to delete.'))
- return super(account_analytic_account, self).unlink(cr, uid, ids, context=context)
+ return super(AccountAnalyticAccount, self).unlink()
+
+ @api.model
+ def _trigger_project_creation(self, vals):
+ res = super(AccountAnalyticAccount, self)._trigger_project_creation(vals)
+ return res or (vals.get('use_issues') and not 'project_creation_in_progress' in self.env.context)
diff --git a/addons/project_issue/models/project_issue.py b/addons/project_issue/models/project_issue.py
index bc929a67352..8d4dacd0caf 100644
--- a/addons/project_issue/models/project_issue.py
+++ b/addons/project_issue/models/project_issue.py
@@ -1,304 +1,223 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-from datetime import datetime
-
-from openerp import api
-from openerp import SUPERUSER_ID
-from openerp import tools
-from openerp.exceptions import UserError, AccessError
-from openerp.osv import fields, osv
-from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
-from openerp.tools.safe_eval import safe_eval as eval
-from openerp.tools.translate import _
+from odoo import api, fields, models, tools, _
+from odoo.exceptions import AccessError
+from odoo.tools.safe_eval import safe_eval as eval
-class project_issue(osv.Model):
+class ProjectIssue(models.Model):
_name = "project.issue"
_description = "Project Issue"
- _order = "priority desc, create_date desc"
_inherit = ['mail.thread', 'ir.needaction_mixin']
+ _order = "priority desc, create_date desc"
_mail_post_access = 'read'
- def _get_default_partner(self, cr, uid, context=None):
- if context is None:
- context = {}
- if 'default_project_id' in context:
- project = self.pool.get('project.project').browse(cr, uid, context['default_project_id'], context=context)
- if project and project.partner_id:
- return project.partner_id.id
- return False
+ @api.model
+ def _get_default_stage_id(self):
+ return self.stage_find(self.env.context.get('default_project_id'), [('fold', '=', False)])
- def _get_default_stage_id(self, cr, uid, context=None):
- """ Gives default stage_id """
- if context is None:
- context = {}
- return self.stage_find(cr, uid, [], context.get('default_project_id'), [('fold', '=', False)], context=context)
+ name = fields.Char(string='Issue', required=True)
+ active = fields.Boolean(default=True)
+ days_since_creation = fields.Integer(compute='_compute_inactivity_days', string='Days since creation date',
+ help="Difference in days between creation date and current date")
+ date_deadline = fields.Date(string='Deadline')
+ partner_id = fields.Many2one('res.partner', string='Contact', index=True)
+ company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env.user.company_id)
+ description = fields.Text('Private Note')
+ kanban_state = fields.Selection([('normal', 'Normal'), ('blocked', 'Blocked'), ('done', 'Ready for next stage')], string='Kanban State',
+ track_visibility='onchange', required=True, default='normal',
+ help="""An Issue's kanban state indicates special situations affecting it:\n
+ * Normal is the default situation\n
+ * Blocked indicates something is preventing the progress of this issue\n
+ * Ready for next stage indicates the issue is ready to be pulled to the next stage""")
+ email_from = fields.Char(string='Email', help="These people will receive email.", index=True)
+ email_cc = fields.Char(string='Watchers Emails', help="""These email addresses will be added to the CC field of all inbound
+ and outbound emails for this record before being sent. Separate multiple email addresses with a comma""")
+ date_open = fields.Datetime(string='Assigned', readonly=True, index=True)
+ date_closed = fields.Datetime(string='Closed', readonly=True, index=True)
+ date = fields.Datetime('Date')
+ date_last_stage_update = fields.Datetime(string='Last Stage Update', index=True, default=fields.Datetime.now)
+ channel = fields.Char(string='Channel', help="Communication channel.")
+ tag_ids = fields.Many2many('project.tags', string='Tags')
+ priority = fields.Selection([('0', 'Low'), ('1', 'Normal'), ('2', 'High')], 'Priority', index=True, default='0')
+ stage_id = fields.Many2one('project.task.type', string='Stage', track_visibility='onchange', index=True,
+ domain="[('project_ids', '=', project_id)]", copy=False,
+ default=_get_default_stage_id)
+ project_id = fields.Many2one('project.project', string='Project', track_visibility='onchange', index=True)
+ duration = fields.Float('Duration')
+ task_id = fields.Many2one('project.task', string='Task', domain="[('project_id','=',project_id)]",
+ help="You can link this issue to an existing task or directly create a new one from here")
+ day_open = fields.Float(compute='_compute_day', string='Days to Assign', store=True)
+ day_close = fields.Float(compute='_compute_day', string='Days to Close', store=True)
- def _read_group_stage_ids(self, cr, uid, ids, domain, read_group_order=None, access_rights_uid=None, context=None):
- if context is None:
- context = {}
- access_rights_uid = access_rights_uid or uid
- stage_obj = self.pool.get('project.task.type')
- order = stage_obj._order
+ user_id = fields.Many2one('res.users', string='Assigned to', index=True, track_visibility='onchange', default=lambda self: self.env.uid)
+ working_hours_open = fields.Float(compute='_compute_day', string='Working Hours to assign the Issue', store=True)
+ working_hours_close = fields.Float(compute='_compute_day', string='Working Hours to close the Issue', store=True)
+ inactivity_days = fields.Integer(compute='_compute_inactivity_days', string='Days since last action',
+ help="Difference in days between last action and current date")
+ color = fields.Integer('Color Index')
+ user_email = fields.Char(related='user_id.email', string='User Email', readonly=True)
+ date_action_last = fields.Datetime(string='Last Action', readonly=True)
+ date_action_next = fields.Datetime(string='Next Action', readonly=True)
+ legend_blocked = fields.Char(related="stage_id.legend_blocked", string='Kanban Blocked Explanation')
+ legend_done = fields.Char(related="stage_id.legend_done", string='Kanban Valid Explanation')
+ legend_normal = fields.Char(related="stage_id.legend_normal", string='Kanban Ongoing Explanation')
+
+ @api.multi
+ def _read_group_stage_ids(self, domain, read_group_order=None, access_rights_uid=None):
+ access_rights_uid = access_rights_uid or self.env.uid
+ ProjectTaskType = self.env['project.task.type']
+ order = ProjectTaskType._order
# lame hack to allow reverting search, should just work in the trivial case
if read_group_order == 'stage_id desc':
order = "%s desc" % order
# retrieve project_id from the context, add them to already fetched columns (ids)
- if 'default_project_id' in context:
- search_domain = ['|', ('project_ids', '=', context['default_project_id']), ('id', 'in', ids)]
+ if 'default_project_id' in self.env.context:
+ search_domain = ['|', ('project_ids', '=', self.env.context['default_project_id']), ('id', 'in', self.ids)]
else:
- search_domain = [('id', 'in', ids)]
+ search_domain = [('id', 'in', self.ids)]
# perform search
- stage_ids = stage_obj._search(cr, uid, search_domain, order=order, access_rights_uid=access_rights_uid, context=context)
- result = stage_obj.name_get(cr, access_rights_uid, stage_ids, context=context)
+ project_task_types = ProjectTaskType.sudo(access_rights_uid).search(search_domain, order=order)
+ result = project_task_types.sudo(access_rights_uid).name_get()
# restore order of the search
- result.sort(lambda x, y: cmp(stage_ids.index(x[0]), stage_ids.index(y[0])))
-
- fold = {}
- for stage in stage_obj.browse(cr, access_rights_uid, stage_ids, context=context):
- fold[stage.id] = stage.fold or False
+ project_task_type_ids = project_task_types.mapped('id')
+ result.sort(lambda x, y: cmp(project_task_type_ids.index(x[0]), project_task_type_ids.index(y[0])))
+ fold = {project_task_type.id: project_task_type.fold for project_task_type in project_task_types}
return result, fold
- def _compute_day(self, cr, uid, ids, fields, args, context=None):
- """
- @param cr: the current row, from the database cursor,
- @param uid: the current user’s ID for security checks,
- @param ids: List of Openday’s IDs
- @return: difference between current date and log date
- @param context: A standard dictionary for contextual values
- """
- Calendar = self.pool['resource.calendar']
-
- res = dict((res_id, {}) for res_id in ids)
- for issue in self.browse(cr, uid, ids, context=context):
- values = {
- 'day_open': 0.0, 'day_close': 0.0,
- 'working_hours_open': 0.0, 'working_hours_close': 0.0,
- 'days_since_creation': 0.0, 'inactivity_days': 0.0,
- }
- # if the working hours on the project are not defined, use default ones (8 -> 12 and 13 -> 17 * 5), represented by None
- calendar_id = None
- if issue.project_id and issue.project_id.resource_calendar_id:
- calendar_id = issue.project_id.resource_calendar_id.id
-
- dt_create_date = datetime.strptime(issue.create_date, DEFAULT_SERVER_DATETIME_FORMAT)
-
- if issue.date_open:
- dt_date_open = datetime.strptime(issue.date_open, DEFAULT_SERVER_DATETIME_FORMAT)
- values['day_open'] = (dt_date_open - dt_create_date).total_seconds() / (24.0 * 3600)
- values['working_hours_open'] = Calendar._interval_hours_get(
- cr, uid, calendar_id, dt_create_date, dt_date_open,
- timezone_from_uid=issue.user_id.id or uid,
- exclude_leaves=False, context=context)
-
- if issue.date_closed:
- dt_date_closed = datetime.strptime(issue.date_closed, DEFAULT_SERVER_DATETIME_FORMAT)
- values['day_close'] = (dt_date_closed - dt_create_date).total_seconds() / (24.0 * 3600)
- values['working_hours_close'] = Calendar._interval_hours_get(
- cr, uid, calendar_id, dt_create_date, dt_date_closed,
- timezone_from_uid=issue.user_id.id or uid,
- exclude_leaves=False, context=context)
-
- days_since_creation = datetime.today() - dt_create_date
- values['days_since_creation'] = days_since_creation.days
- if issue.date_action_last:
- inactive_days = datetime.today() - datetime.strptime(issue.date_action_last, DEFAULT_SERVER_DATETIME_FORMAT)
- elif issue.date_last_stage_update:
- inactive_days = datetime.today() - datetime.strptime(issue.date_last_stage_update, DEFAULT_SERVER_DATETIME_FORMAT)
- else:
- inactive_days = datetime.today() - datetime.strptime(issue.create_date, DEFAULT_SERVER_DATETIME_FORMAT)
- values['inactivity_days'] = inactive_days.days
-
- # filter only required values
- for field in fields:
- res[issue.id][field] = values[field]
-
- return res
-
- def on_change_project(self, cr, uid, ids, project_id, context=None):
- if project_id:
- project = self.pool.get('project.project').browse(cr, uid, project_id, context=context)
- if project and project.partner_id:
- return {'value': {'partner_id': project.partner_id.id, 'email_from': project.partner_id.email}}
- return {}
-
- _columns = {
- 'id': fields.integer('ID', readonly=True),
- 'name': fields.char('Issue', required=True),
- 'active': fields.boolean('Active', required=False),
- 'create_date': fields.datetime('Creation Date', readonly=True, select=True),
- 'write_date': fields.datetime('Update Date', readonly=True),
- 'days_since_creation': fields.function(_compute_day, string='Days since creation date', \
- multi='compute_day', type="integer", help="Difference in days between creation date and current date",
- groups='base.group_user'),
- 'date_deadline': fields.date('Deadline'),
- 'partner_id': fields.many2one('res.partner', 'Contact', select=1),
- 'company_id': fields.many2one('res.company', 'Company'),
- 'description': fields.text('Private Note'),
- 'kanban_state': fields.selection([('normal', 'Normal'),('blocked', 'Blocked'),('done', 'Ready for next stage')], 'Kanban State',
- track_visibility='onchange',
- help="A Issue's kanban state indicates special situations affecting it:\n"
- " * Normal is the default situation\n"
- " * Blocked indicates something is preventing the progress of this issue\n"
- " * Ready for next stage indicates the issue is ready to be pulled to the next stage",
- required=True),
- 'email_from': fields.char('Email', size=128, help="These people will receive email.", select=1),
- 'email_cc': fields.char('Watchers Emails', size=256, help="These email addresses will be added to the CC field of all inbound and outbound emails for this record before being sent. Separate multiple email addresses with a comma"),
- 'date_open': fields.datetime('Assigned', readonly=True, select=True),
- # Project Issue fields
- 'date_closed': fields.datetime('Closed', readonly=True, select=True),
- 'date': fields.datetime('Date'),
- 'date_last_stage_update': fields.datetime('Last Stage Update', select=True),
- 'channel': fields.char('Channel', help="Communication channel."),
- 'tag_ids': fields.many2many('project.tags', string='Tags'),
- 'priority': fields.selection([('0','Low'), ('1','Normal'), ('2','High')], 'Priority', select=True),
- 'stage_id': fields.many2one ('project.task.type', 'Stage',
- track_visibility='onchange', select=True,
- domain="[('project_ids', '=', project_id)]", copy=False),
- 'project_id': fields.many2one('project.project', 'Project', track_visibility='onchange', select=True),
- 'duration': fields.float('Duration'),
- 'task_id': fields.many2one('project.task', 'Task', domain="[('project_id','=',project_id)]",
- help="You can link this issue to an existing task or directly create a new one from here"),
- 'day_open': fields.function(_compute_day, string='Days to Assign',
- multi='compute_day', type="float",
- store={'project.issue': (lambda self, cr, uid, ids, c={}: ids, ['date_open'], 10)},
- groups='base.group_user'),
- 'day_close': fields.function(_compute_day, string='Days to Close',
- multi='compute_day', type="float",
- store={'project.issue': (lambda self, cr, uid, ids, c={}: ids, ['date_closed'], 10)},
- groups='base.group_user'),
- 'user_id': fields.many2one('res.users', 'Assigned to', required=False, select=1, track_visibility='onchange'),
- 'working_hours_open': fields.function(_compute_day, string='Working Hours to assign the Issue',
- multi='compute_day', type="float",
- store={'project.issue': (lambda self, cr, uid, ids, c={}: ids, ['date_open'], 10)},
- groups='base.group_user'),
- 'working_hours_close': fields.function(_compute_day, string='Working Hours to close the Issue',
- multi='compute_day', type="float",
- store={'project.issue': (lambda self, cr, uid, ids, c={}: ids, ['date_closed'], 10)},
- groups='base.group_user'),
- 'inactivity_days': fields.function(_compute_day, string='Days since last action',
- multi='compute_day', type="integer", help="Difference in days between last action and current date",
- groups='base.group_user'),
- 'color': fields.integer('Color Index'),
- 'user_email': fields.related('user_id', 'email', type='char', string='User Email', readonly=True),
- 'date_action_last': fields.datetime('Last Action', readonly=1),
- 'date_action_next': fields.datetime('Next Action', readonly=1),
- 'legend_blocked': fields.related("stage_id", "legend_blocked", type="char", string='Kanban Blocked Explanation'),
- 'legend_done': fields.related("stage_id", "legend_done", type="char", string='Kanban Valid Explanation'),
- 'legend_normal': fields.related("stage_id", "legend_normal", type="char", string='Kanban Ongoing Explanation'),
- }
-
- _defaults = {
- 'active': 1,
- 'stage_id': lambda s, cr, uid, c: s._get_default_stage_id(cr, uid, c),
- 'company_id': lambda s, cr, uid, c: s.pool['res.users']._get_company(cr, uid, context=c),
- 'priority': '0',
- 'kanban_state': 'normal',
- 'date_last_stage_update': fields.datetime.now,
- 'user_id': lambda obj, cr, uid, context: uid,
- }
-
_group_by_full = {
'stage_id': _read_group_stage_ids
}
- def copy(self, cr, uid, id, default=None, context=None):
- issue = self.read(cr, uid, [id], ['name'], context=context)[0]
- if not default:
- default = {}
- default = default.copy()
- default.update(name=_('%s (copy)') % (issue['name']))
- return super(project_issue, self).copy(cr, uid, id, default=default, context=context)
+ @api.multi
+ @api.depends('create_date', 'date_closed', 'date_open')
+ def _compute_day(self):
+ for issue in self:
+ # if the working hours on the project are not defined, use default ones (8 -> 12 and 13 -> 17 * 5), represented by None
+ calendar_id = None
+ if issue.project_id and issue.project_id.resource_calendar_id:
+ calendar_id = issue.project_id.resource_calendar_id.id
- def create(self, cr, uid, vals, context=None):
- context = dict(context or {})
- if vals.get('project_id') and not context.get('default_project_id'):
+ dt_create_date = fields.Datetime.from_string(issue.create_date)
+ if issue.date_open:
+ dt_date_open = fields.Datetime.from_string(issue.date_open)
+ issue.day_open = (dt_date_open - dt_create_date).total_seconds() / (24.0 * 3600)
+ # FIXME when resource.calendar is migrated to new api
+ # Old api call because get_working_hours uses get_weekdays
+ # which has a different behavior if the given id is None.
+ issue.working_hours_open = self.pool['resource.calendar'].get_working_hours(
+ self.env.cr, self.env.uid, calendar_id, dt_create_date, dt_date_open,
+ compute_leaves=True, resource_id=False, default_interval=(8, 16),
+ context=self.env.context
+ )
+
+ if issue.date_closed:
+ dt_date_closed = fields.Datetime.from_string(issue.date_closed)
+ issue.day_close = (dt_date_closed - dt_create_date).total_seconds() / (24.0 * 3600)
+ # FIXME when resource.calendar is migrated to new api
+ # Old api call because get_working_hours uses get_weekdays
+ # which has a different behavior if the given id is None.
+ issue.working_hours_close = self.pool['resource.calendar'].get_working_hours(
+ self.env.cr, self.env.uid, calendar_id, dt_create_date, dt_date_closed,
+ compute_leaves=True, resource_id=False, default_interval=(8, 16),
+ context=self.env.context
+ )
+
+ @api.multi
+ @api.depends('create_date', 'date_action_last', 'date_last_stage_update')
+ def _compute_inactivity_days(self):
+ current_datetime = fields.Datetime.from_string(fields.Datetime.now())
+ for issue in self:
+ dt_create_date = fields.Datetime.from_string(issue.create_date)
+ issue.days_since_creation = (current_datetime - dt_create_date).days
+
+ if issue.date_action_last:
+ issue.inactivity_days = (current_datetime - fields.Datetime.from_string(issue.date_action_last)).days
+ elif issue.date_last_stage_update:
+ issue.inactivity_days = (current_datetime - fields.Datetime.from_string(issue.date_last_stage_update)).days
+ else:
+ issue.inactivity_days = (current_datetime - dt_create_date).days
+
+ @api.onchange('partner_id')
+ def _onchange_partner_id(self):
+ """ This function sets partner email address based on partner
+ """
+ self.email_from = self.partner_id.email
+
+ @api.onchange('project_id')
+ def _onchange_project_id(self):
+ if self.project_id:
+ self.partner_id = self.project_id.partner_id.id
+ self.email_from = self.project_id.partner_id.email
+
+ @api.onchange('task_id')
+ def _onchange_task_id(self):
+ self.user_id = self.task_id.user_id
+
+ @api.one
+ def copy(self, default=None):
+ if default is None:
+ default = {}
+ default.update(name=_('%s (copy)') % (self.name))
+ return super(ProjectIssue, self).copy(default=default)
+
+ @api.model
+ def create(self, vals):
+ context = dict(self.env.context)
+ if vals.get('project_id') and not self.env.context.get('default_project_id'):
context['default_project_id'] = vals.get('project_id')
if vals.get('user_id') and not vals.get('date_open'):
- vals['date_open'] = fields.datetime.now()
+ vals['date_open'] = fields.Datetime.now()
if 'stage_id' in vals:
- vals.update(self.onchange_stage_id(cr, uid, None, vals.get('stage_id'), context=context)['value'])
+ vals.update(self.update_date_closed(vals['stage_id']))
# context: no_log, because subtype already handle this
- create_context = dict(context, mail_create_nolog=True)
- return super(project_issue, self).create(cr, uid, vals, context=create_context)
+ context['mail_create_nolog'] = True
+ return super(ProjectIssue, self.with_context(context)).create(vals)
- def write(self, cr, uid, ids, vals, context=None):
+ @api.multi
+ def write(self, vals):
# stage change: update date_last_stage_update
if 'stage_id' in vals:
- vals.update(self.onchange_stage_id(cr, uid, ids, vals.get('stage_id'), context=context)['value'])
- vals['date_last_stage_update'] = fields.datetime.now()
+ vals.update(self.update_date_closed(vals['stage_id']))
+ vals['date_last_stage_update'] = fields.Datetime.now()
if 'kanban_state' not in vals:
vals['kanban_state'] = 'normal'
# user_id change: update date_open
if vals.get('user_id') and 'date_open' not in vals:
- vals['date_open'] = fields.datetime.now()
+ vals['date_open'] = fields.Datetime.now()
+ return super(ProjectIssue, self).write(vals)
- return super(project_issue, self).write(cr, uid, ids, vals, context)
-
- def onchange_task_id(self, cr, uid, ids, task_id, context=None):
- if not task_id:
- return {'value': {}}
- task = self.pool.get('project.task').browse(cr, uid, task_id, context=context)
- return {'value': {'user_id': task.user_id.id, }}
-
- def onchange_partner_id(self, cr, uid, ids, partner_id, context=None):
- """ This function returns value of partner email address based on partner
- :param part: Partner's id
- """
- if partner_id:
- partner = self.pool['res.partner'].browse(cr, uid, partner_id, context)
- return {'value': {'email_from': partner.email}}
- return {'value': {'email_from': False}}
-
- def get_empty_list_help(self, cr, uid, help, context=None):
- context = dict(context or {})
- context['empty_list_help_model'] = 'project.project'
- context['empty_list_help_id'] = context.get('default_project_id')
- context['empty_list_help_document_name'] = _("issues")
- return super(project_issue, self).get_empty_list_help(cr, uid, help, context=context)
+ @api.model
+ def get_empty_list_help(self, help):
+ return super(ProjectIssue, self.with_context(
+ empty_list_help_model='project.project',
+ empty_list_help_id=self.env.context.get('default_project_id'),
+ empty_list_help_document_name=_("issues")
+ )).get_empty_list_help(help)
# -------------------------------------------------------
# Stage management
# -------------------------------------------------------
- def onchange_stage_id(self, cr, uid, ids, stage_id, context=None):
- if not stage_id:
- return {'value': {}}
- stage = self.pool['project.task.type'].browse(cr, uid, stage_id, context=context)
- if stage.fold:
- return {'value': {'date_closed': fields.datetime.now()}}
- return {'value': {'date_closed': False}}
+ def update_date_closed(self, stage_id):
+ project_task_type = self.env['project.task.type'].browse(stage_id)
+ if project_task_type.fold:
+ return {'date_closed': fields.Datetime.now()}
+ return {'date_closed': False}
- def stage_find(self, cr, uid, cases, project_id, domain=[], order='sequence', context=None):
+ def stage_find(self, project_id, domain=None, order='sequence'):
""" Override of the base.stage method
Parameter of the stage search taken from the issue:
- - type: stage type must be the same or 'both'
- project_id: if set, stages must belong to this project or
be a default case
"""
- if isinstance(cases, (int, long)):
- cases = self.browse(cr, uid, cases, context=context)
- # collect all project_ids
- project_ids = []
+ search_domain = list(domain) if domain else []
if project_id:
- project_ids.append(project_id)
- for task in cases:
- if task.project_id:
- project_ids.append(task.project_id.id)
- # OR all project_ids and OR with case_default
- search_domain = []
- if project_ids:
- search_domain += [('|')] * (len(project_ids) - 1)
- for project_id in project_ids:
- search_domain.append(('project_ids', '=', project_id))
- search_domain += list(domain)
- # perform search, return the first found
- stage_ids = self.pool.get('project.task.type').search(cr, uid, search_domain, order=order, context=context)
- if stage_ids:
- return stage_ids[0]
- return False
+ search_domain += [('project_ids', '=', project_id)]
+ project_task_type = self.env['project.task.type'].search(search_domain, order=order, limit=1)
+ return project_task_type.id
# -------------------------------------------------------
# Mail gateway
@@ -306,57 +225,57 @@ class project_issue(osv.Model):
@api.multi
def _track_template(self, tracking):
- res = super(project_issue, self)._track_template(tracking)
- test_issue = self[0]
- changes, dummy = tracking[test_issue.id]
- if 'stage_id' in changes and test_issue.stage_id.mail_template_id:
- res['stage_id'] = (test_issue.stage_id.mail_template_id, {'composition_mode': 'mass_mail'})
+ self.ensure_one()
+ res = super(ProjectIssue, self)._track_template(tracking)
+ changes, dummy = tracking[self.id]
+ if 'stage_id' in changes and self.stage_id.mail_template_id:
+ res['stage_id'] = (self.stage_id.mail_template_id, {'composition_mode': 'mass_mail'})
return res
- def _track_subtype(self, cr, uid, ids, init_values, context=None):
- record = self.browse(cr, uid, ids[0], context=context)
- if 'kanban_state' in init_values and record.kanban_state == 'blocked':
+ def _track_subtype(self, init_values):
+ self.ensure_one()
+ if 'kanban_state' in init_values and self.kanban_state == 'blocked':
return 'project_issue.mt_issue_blocked'
- elif 'kanban_state' in init_values and record.kanban_state == 'done':
+ elif 'kanban_state' in init_values and self.kanban_state == 'done':
return 'project_issue.mt_issue_ready'
- elif 'user_id' in init_values and record.user_id: # assigned -> new
+ elif 'user_id' in init_values and self.user_id: # assigned -> new
return 'project_issue.mt_issue_new'
- elif 'stage_id' in init_values and record.stage_id and record.stage_id.sequence <= 1: # start stage -> new
+ elif 'stage_id' in init_values and self.stage_id and self.stage_id.sequence <= 1: # start stage -> new
return 'project_issue.mt_issue_new'
elif 'stage_id' in init_values:
return 'project_issue.mt_issue_stage'
- return super(project_issue, self)._track_subtype(cr, uid, ids, init_values, context=context)
+ return super(ProjectIssue, self)._track_subtype(init_values)
- def _notification_group_recipients(self, cr, uid, ids, message, recipients, done_ids, group_data, context=None):
+ @api.multi
+ def _notification_group_recipients(self, message, recipients, done_ids, group_data):
""" Override the mail.thread method to handle project users and officers
recipients. Indeed those will have specific action in their notification
emails: creating tasks, assigning it. """
- group_project_user = self.pool['ir.model.data'].xmlid_to_res_id(cr, uid, 'project.group_project_user')
- group_user = self.pool['ir.model.data'].xmlid_to_res_id(cr, uid, 'base.group_user')
+ group_project_user_id = self.env.ref('project.group_project_user').id
for recipient in recipients:
if recipient.id in done_ids:
continue
- if recipient.user_ids and group_project_user in recipient.user_ids[0].groups_id.ids:
+ if recipient.user_ids and group_project_user_id in recipient.user_ids[0].groups_id.ids:
group_data['group_project_user'] |= recipient
elif not recipient.user_ids:
group_data['partner'] |= recipient
else:
group_data['user'] |= recipient
done_ids.add(recipient.id)
- return super(project_issue, self)._notification_group_recipients(cr, uid, ids, message, recipients, done_ids, group_data, context=context)
+ return super(ProjectIssue, self)._notification_group_recipients(message, recipients, done_ids, group_data)
- def _notification_get_recipient_groups(self, cr, uid, ids, message, recipients, context=None):
- res = super(project_issue, self)._notification_get_recipient_groups(cr, uid, ids, message, recipients, context=context)
+ @api.multi
+ def _notification_get_recipient_groups(self, message, recipients):
+ self.ensure_one()
+ res = super(ProjectIssue, self)._notification_get_recipient_groups(message, recipients)
- new_action_id = self.pool['ir.model.data'].xmlid_to_res_id(cr, uid, 'project_issue.project_issue_categ_act0')
- take_action = self._notification_link_helper(cr, uid, ids, 'assign', context=context)
- new_action = self._notification_link_helper(cr, uid, ids, 'new', context=context, action_id=new_action_id)
-
- task_record = self.browse(cr, uid, ids[0], context=context)
actions = []
- if not task_record.user_id:
+ if not self.user_id:
+ take_action = self._notification_link_helper('assign')
actions.append({'url': take_action, 'title': _('I take it')})
else:
+ new_action_id = self.env.ref('project_issue.project_issue_categ_act0').id
+ new_action = self._notification_link_helper('new', action_id=new_action_id)
actions.append({'url': new_action, 'title': _('New Issue')})
res['group_project_user'] = {
@@ -364,18 +283,19 @@ class project_issue(osv.Model):
}
return res
- @api.cr_uid_context
- def message_get_reply_to(self, cr, uid, ids, default=None, context=None):
+ @api.model
+ def message_get_reply_to(self, res_ids, default=None):
""" Override to get the reply_to of the parent project. """
- issues = self.browse(cr, SUPERUSER_ID, ids, context=context)
- project_ids = set([issue.project_id.id for issue in issues if issue.project_id])
- aliases = self.pool['project.project'].message_get_reply_to(cr, uid, list(project_ids), default=default, context=context)
+ issues = self.browse(res_ids)
+ project_ids = set(issues.mapped('project_id').ids)
+ aliases = self.env['project.project'].message_get_reply_to(list(project_ids), default=default)
return dict((issue.id, aliases.get(issue.project_id and issue.project_id.id or 0, False)) for issue in issues)
- def message_get_suggested_recipients(self, cr, uid, ids, context=None):
- recipients = super(project_issue, self).message_get_suggested_recipients(cr, uid, ids, context=context)
+ @api.multi
+ def message_get_suggested_recipients(self):
+ recipients = super(ProjectIssue, self).message_get_suggested_recipients()
try:
- for issue in self.browse(cr, uid, ids, context=context):
+ for issue in self:
if issue.partner_id:
issue._message_add_suggested_recipient(recipients, partner=issue.partner_id, reason=_('Customer'))
elif issue.email_from:
@@ -384,14 +304,14 @@ class project_issue(osv.Model):
pass
return recipients
- def email_split(self, cr, uid, ids, msg, context=None):
+ @api.multi
+ def email_split(self, msg):
email_list = tools.email_split((msg.get('to') or '') + ',' + (msg.get('cc') or ''))
# check left-part is not already an alias
- issue_ids = self.browse(cr, uid, ids, context=context)
- aliases = [issue.project_id.alias_name for issue in issue_ids if issue.project_id]
- return filter(lambda x: x.split('@')[0] not in aliases, email_list)
+ return filter(lambda x: x.split('@')[0] not in self.mapped('project_id.alias_name'), email_list)
- def message_new(self, cr, uid, msg, custom_values=None, context=None):
+ @api.model
+ def message_new(self, msg, custom_values=None):
""" Overrides mail_thread message_new that is called by the mailgateway
through message_process.
This override updates the document according to the email.
@@ -400,60 +320,59 @@ class project_issue(osv.Model):
# do not want to explicitly set user_id to False; however we do not
# want the gateway user to be responsible if no other responsible is
# found.
- create_context = dict(context or {})
+ create_context = dict(self.env.context or {})
create_context['default_user_id'] = False
- if custom_values is None:
- custom_values = {}
- context = dict(context or {}, state_to='draft')
defaults = {
'name': msg.get('subject') or _("No Subject"),
'email_from': msg.get('from'),
'email_cc': msg.get('cc'),
'partner_id': msg.get('author_id', False),
}
- defaults.update(custom_values)
+ if custom_values:
+ defaults.update(custom_values)
- res_id = super(project_issue, self).message_new(cr, uid, msg, custom_values=defaults, context=create_context)
- email_list = self.email_split(cr, uid, [res_id], msg, context=context)
- partner_ids = filter(None, self._find_partner_from_emails(cr, uid, [res_id], email_list, force_create=False, context=context))
- self.message_subscribe(cr, uid, [res_id], partner_ids, context=context)
+ res_id = super(ProjectIssue, self.with_context(create_context)).message_new(msg, custom_values=defaults)
+ issue = self.browse(res_id)
+ email_list = issue.email_split(msg)
+ partner_ids = filter(None, issue._find_partner_from_emails(email_list))
+ issue.message_subscribe(partner_ids)
return res_id
- def message_update(self, cr, uid, ids, msg, update_vals=None, context=None):
+ @api.multi
+ def message_update(self, msg, update_vals=None):
""" Override to update the issue according to the email. """
+ email_list = self.email_split(msg)
+ partner_ids = filter(None, self._find_partner_from_emails(email_list))
+ self.message_subscribe(partner_ids)
+ return super(ProjectIssue, self).message_update(msg, update_vals=update_vals)
- email_list = self.email_split(cr, uid, ids, msg, context=context)
- partner_ids = filter(None, self._find_partner_from_emails(cr, uid, ids, email_list, force_create=False, context=context))
- self.message_subscribe(cr, uid, ids, partner_ids, context=context)
- return super(project_issue, self).message_update(cr, uid, ids, msg, update_vals=update_vals, context=context)
-
- @api.cr_uid_ids_context
+ @api.multi
@api.returns('mail.message', lambda value: value.id)
- def message_post(self, cr, uid, thread_id, subtype=None, context=None, **kwargs):
+ def message_post(self, subtype=None, **kwargs):
""" Overrides mail_thread message_post so that we can set the date of last action field when
a new message is posted on the issue.
"""
- if context is None:
- context = {}
- res = super(project_issue, self).message_post(cr, uid, thread_id, subtype=subtype, context=context, **kwargs)
- if thread_id and subtype:
- self.write(cr, SUPERUSER_ID, thread_id, {'date_action_last': fields.datetime.now()}, context=context)
- return res
+ self.ensure_one()
+ mail_message = super(ProjectIssue, self).message_post(subtype=subtype, **kwargs)
+ if subtype:
+ self.sudo().write({'date_action_last': fields.Datetime.now()})
+ return mail_message
- def message_get_email_values(self, cr, uid, ids, notif_mail=None, context=None):
- res = super(project_issue, self).message_get_email_values(cr, uid, ids, notif_mail=notif_mail, context=context)
- current_issue = self.browse(cr, uid, ids[0], context=context)
+ @api.multi
+ def message_get_email_values(self, notif_mail=None):
+ self.ensure_one()
+ res = super(ProjectIssue, self).message_get_email_values(notif_mail=notif_mail)
headers = {}
if res.get('headers'):
try:
headers.update(eval(res['headers']))
except Exception:
pass
- if current_issue.project_id:
+ if self.project_id:
current_objects = filter(None, headers.get('X-Odoo-Objects', '').split(','))
- current_objects.insert(0, 'project.project-%s, ' % current_issue.project_id.id)
+ current_objects.insert(0, 'project.project-%s, ' % self.project_id.id)
headers['X-Odoo-Objects'] = ','.join(current_objects)
- if current_issue.tag_ids:
- headers['X-Odoo-Tags'] = ','.join([tag.name for tag in current_issue.tag_ids])
+ if self.tag_ids:
+ headers['X-Odoo-Tags'] = ','.join(self.tag_ids.mapped('name'))
res['headers'] = repr(headers)
return res
diff --git a/addons/project_issue/models/project_project.py b/addons/project_issue/models/project_project.py
index 490c837f700..acc0b37b1a4 100644
--- a/addons/project_issue/models/project_project.py
+++ b/addons/project_issue/models/project_project.py
@@ -1,70 +1,43 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-from openerp.osv import fields, osv
-from openerp import api
+from odoo import api, fields, models
-class project_project(osv.Model):
+class Project(models.Model):
_inherit = 'project.project'
- def _get_alias_models(self, cr, uid, context=None):
- res = super(project, self)._get_alias_models(cr, uid, context=context)
+ issue_count = fields.Integer(compute='_compute_issue_count', string="Issues")
+ issue_ids = fields.One2many('project.issue', 'project_id', string="Issues", domain=['|', ('stage_id.fold', '=', False), ('stage_id', '=', False)])
+ label_issues = fields.Char(string='Use Issues as', help="Customize the issues label, for example to call them cases.", default='Issues')
+ use_issues = fields.Boolean(related="analytic_account_id.use_issues", default=True)
+ issue_needaction_count = fields.Integer(compute="_issue_needaction_count", string="Issues")
+
+ @api.model
+ def _get_alias_models(self):
+ res = super(Project, self)._get_alias_models()
res.append(("project.issue", "Issues"))
return res
+ def _compute_issue_count(self):
+ self.issue_count = self.env['project.issue'].search_count([('project_id', '=', self.id), '|', ('stage_id.fold', '=', False), ('stage_id', '=', False)])
- def _issue_count(self, cr, uid, ids, field_name, arg, context=None):
- Issue = self.pool['project.issue']
- return {
- project_id: Issue.search_count(cr,uid, [('project_id', '=', project_id), '|', ('stage_id.fold', '=', False), ('stage_id', '=', False)], context=context)
- for project_id in ids
- }
+ def _issue_needaction_count(self):
+ issue_data = self.env['project.issue'].read_group([('project_id', 'in', self.ids), ('message_needaction', '=', True)], ['project_id'], ['project_id'])
+ result = dict((data['project_id'][0], data['project_id_count']) for data in issue_data)
+ for project in self:
+ project.issue_needaction_count = int(result.get(project.id, 0))
- def _issue_needaction_count(self, cr, uid, ids, field_name, arg, context=None):
- Issue = self.pool['project.issue']
- res = dict.fromkeys(ids, 0)
- projects = Issue.read_group(cr, uid, [('project_id', 'in', ids), ('message_needaction', '=', True)], ['project_id'], ['project_id'], context=context)
- res.update({project['project_id'][0]: int(project['project_id_count']) for project in projects})
- return res
-
- _columns = {
- 'issue_count': fields.function(_issue_count, type='integer', string="Issues",),
- 'issue_ids': fields.one2many('project.issue', 'project_id', string="Issues",
- domain=['|', ('stage_id.fold', '=', False), ('stage_id', '=', False)]),
- 'issue_needaction_count': fields.function(_issue_needaction_count, type='integer', string="Issues",),
- 'label_issues': fields.char('Use Issues as', help="Customize the issues label, for example to call them cases."),
- }
-
- _defaults = {
- 'use_issues': True,
- 'label_issues': 'Issues',
- }
-
- def _check_create_write_values(self, cr, uid, vals, context=None):
- """ Perform some check on values given to create or write. """
- # Handle use_tasks / use_issues: if only one is checked, alias should take the same model
- if vals.get('use_tasks') and not vals.get('use_issues'):
- vals['alias_model'] = 'project.task'
- elif vals.get('use_issues') and not vals.get('use_tasks'):
- vals['alias_model'] = 'project.issue'
-
- def on_change_use_tasks_or_issues(self, cr, uid, ids, use_tasks, use_issues, context=None):
- values = {}
- if use_tasks and not use_issues:
- values['alias_model'] = 'project.task'
- elif not use_tasks and use_issues:
- values['alias_model'] = 'project.issue'
- return {'value': values}
-
- def create(self, cr, uid, vals, context=None):
- self._check_create_write_values(cr, uid, vals, context=context)
- return super(project_project, self).create(cr, uid, vals, context=context)
+ @api.onchange('use_issues', 'use_tasks')
+ def _on_change_use_tasks_or_issues(self):
+ if self.use_tasks and not self.use_issues:
+ self.alias_model = 'project.task'
+ elif not self.use_tasks and self.use_issues:
+ self.alias_model = 'project.issue'
@api.multi
def write(self, vals):
- self._check_create_write_values(vals)
- res = super(project_project, self).write(vals)
+ res = super(Project, self).write(vals)
if 'active' in vals:
# archiving/unarchiving a project does it on its issues, too
issues = self.with_context(active_test=False).mapped('issue_ids')
diff --git a/addons/project_issue/models/project_task_type.py b/addons/project_issue/models/project_task_type.py
index e214a2c7ef3..07515890ba0 100644
--- a/addons/project_issue/models/project_task_type.py
+++ b/addons/project_issue/models/project_task_type.py
@@ -1,10 +1,10 @@
# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
-from openerp import models
+from odoo import models
class ProjectStage(models.Model):
- _name = "project.task.type"
_inherit = ['project.task.type']
def _get_mail_template_id_domain(self):
diff --git a/addons/project_issue/models/res_partner.py b/addons/project_issue/models/res_partner.py
index 7e9655a41c9..2ec0f77a5f7 100644
--- a/addons/project_issue/models/res_partner.py
+++ b/addons/project_issue/models/res_partner.py
@@ -1,21 +1,16 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-from openerp.osv import fields, osv
+from odoo import fields, models
-class res_partner(osv.osv):
- """ Inherits partner and adds Issue information in the partner form """
+class ResPartner(models.Model):
_inherit = 'res.partner'
- def _issue_count(self, cr, uid, ids, field_name, arg, context=None):
- Issue = self.pool['project.issue']
- partners = {id: self.search(cr, uid, [('id', 'child_of', ids)]) for id in ids}
- return {
- partner_id: Issue.search_count(cr, uid, [('partner_id', 'in', partners[partner_id])])
- for partner_id in partners.keys()
- }
+ issue_count = fields.Integer(compute='_compute_issue_count', string='# Issues')
- _columns = {
- 'issue_count': fields.function(_issue_count, string='# Issues', type='integer'),
- }
+ def _compute_issue_count(self):
+ Issue = self.pool['project.issue']
+ partners = {id: self.search([('id', 'child_of', self.ids)]) for id in self.ids}
+ for partner_id in partners.keys():
+ partner.issue_count = Issue.search_count([('partner_id', 'in', partners[partner_id])])
diff --git a/addons/project_issue/report/project_issue_report.py b/addons/project_issue/report/project_issue_report.py
index 6a6b2b07e99..63d5281d885 100644
--- a/addons/project_issue/report/project_issue_report.py
+++ b/addons/project_issue/report/project_issue_report.py
@@ -1,38 +1,33 @@
-
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-from openerp.osv import fields, osv
-from openerp import tools
+from odoo import fields, models, tools
-class project_issue_report(osv.osv):
+class ProjectIssueReport(models.Model):
_name = "project.issue.report"
_auto = False
- _columns = {
- 'company_id': fields.many2one('res.company', 'Company', readonly=True),
- 'opening_date': fields.datetime('Date of Opening', readonly=True),
- 'create_date': fields.datetime('Create Date', readonly=True),
- 'date_closed': fields.datetime('Date of Closing', readonly=True),
- 'date_last_stage_update': fields.datetime('Last Stage Update', readonly=True),
- 'stage_id': fields.many2one('project.task.type', 'Stage'),
- 'nbr': fields.integer('# of Issues', readonly=True), # TDE FIXME master: rename into nbr_issues
- 'working_hours_open': fields.float('Avg. Working Hours to Open', readonly=True, group_operator="avg"),
- 'working_hours_close': fields.float('Avg. Working Hours to Close', readonly=True, group_operator="avg"),
- 'delay_open': fields.float('Avg. Delay to Open', digits=(16,2), readonly=True, group_operator="avg",
- help="Number of Days to open the project issue."),
- 'delay_close': fields.float('Avg. Delay to Close', digits=(16,2), readonly=True, group_operator="avg",
- help="Number of Days to close the project issue"),
- 'company_id' : fields.many2one('res.company', 'Company'),
- 'priority': fields.selection([('0','Low'), ('1','Normal'), ('2','High')], 'Priority'),
- 'project_id':fields.many2one('project.project', 'Project',readonly=True),
- 'user_id' : fields.many2one('res.users', 'Assigned to',readonly=True),
- 'partner_id': fields.many2one('res.partner','Contact'),
- 'channel': fields.char('Channel', readonly=True, help="Communication Channel."),
- 'task_id': fields.many2one('project.task', 'Task'),
- 'email': fields.integer('# Emails', size=128, readonly=True),
- }
+ company_id = fields.Many2one('res.company', 'Company', readonly=True)
+ opening_date = fields.Datetime('Date of Opening', readonly=True)
+ create_date = fields.Datetime('Create Date', readonly=True)
+ date_closed = fields.Datetime('Date of Closing', readonly=True)
+ date_last_stage_update = fields.Datetime('Last Stage Update', readonly=True)
+ stage_id = fields.Many2one('project.task.type', 'Stage')
+ nbr = fields.Integer('# of Issues', readonly=True) # TDE FIXME master: rename into nbr_issues
+ working_hours_open = fields.Float('Avg. Working Hours to Open', readonly=True, group_operator="avg")
+ working_hours_close = fields.Float('Avg. Working Hours to Close', readonly=True, group_operator="avg")
+ delay_open = fields.Float('Avg. Delay to Open', digits=(16, 2), readonly=True, group_operator="avg",
+ help="Number of Days to open the project issue.")
+ delay_close = fields.Float('Avg. Delay to Close', digits=(16, 2), readonly=True, group_operator="avg",
+ help="Number of Days to close the project issue")
+ priority = fields.Selection([('0', 'Low'), ('1', 'Normal'), ('2', 'High')])
+ project_id = fields.Many2one('project.project', 'Project', readonly=True)
+ user_id = fields.Many2one('res.users', 'Assigned to', readonly=True)
+ partner_id = fields.Many2one('res.partner', 'Contact')
+ channel = fields.Char('Channel', readonly=True, help="Communication Channel.")
+ task_id = fields.Many2one('project.task', 'Task')
+ email = fields.Integer('# Emails', readonly=True)
def init(self, cr):
tools.drop_view_if_exists(cr, 'project_issue_report')
diff --git a/addons/project_issue/report/project_issue_report_view.xml b/addons/project_issue/report/project_issue_report_view.xml
index 5c8b58f8619..69dde2a4e32 100644
--- a/addons/project_issue/report/project_issue_report_view.xml
+++ b/addons/project_issue/report/project_issue_report_view.xml
@@ -1,6 +1,5 @@
-
-
+project.issue.report.pivotproject.issue.report
@@ -93,9 +92,6 @@
This report on the project issues allows you to analyse the quality of your support or after-sales services. You can track the issues per age. You can analyse the time required to open or close an issue, the number of email to exchange and the time spent on average by issues.
-
-
-
-
-
+
diff --git a/addons/project_issue/security/project_issue_security.xml b/addons/project_issue/security/project_issue_security.xml
index bb1e519f604..f6608700ab9 100644
--- a/addons/project_issue/security/project_issue_security.xml
+++ b/addons/project_issue/security/project_issue_security.xml
@@ -1,15 +1,15 @@
-
+
-
+ Project/Issue: project manager: see all[(1, '=', 1)]
-
+ Project/Issue: employees: employee or (followers and following)['|',
@@ -46,4 +46,4 @@
-
+
diff --git a/addons/project_issue/views/account_analytic_account_view.xml b/addons/project_issue/views/account_analytic_account_view.xml
index c1f787abecb..8512cdc677c 100644
--- a/addons/project_issue/views/account_analytic_account_view.xml
+++ b/addons/project_issue/views/account_analytic_account_view.xml
@@ -1,17 +1,15 @@
-
-
-
-
- account.analytic.account.issue.form.inherit
- account.analytic.account
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+ account.analytic.account.issue.form.inherit
+ account.analytic.account
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/project_issue/views/project_dashboard.xml b/addons/project_issue/views/project_dashboard.xml
index b7197fa086d..0138a09f382 100644
--- a/addons/project_issue/views/project_dashboard.xml
+++ b/addons/project_issue/views/project_dashboard.xml
@@ -1,3 +1,4 @@
+
project.project.kanban.inherited
diff --git a/addons/project_issue/views/project_issue_menu.xml b/addons/project_issue/views/project_issue_menu.xml
index 020ad61a787..675b5061af4 100644
--- a/addons/project_issue/views/project_issue_menu.xml
+++ b/addons/project_issue/views/project_issue_menu.xml
@@ -1,11 +1,9 @@
-
-
-
-
+
+
-
+ Issuesproject.issueform
@@ -16,49 +14,48 @@
- The Odoo issues tacker allows you to efficiently manage things
+ The Odoo issues tracker allows you to efficiently manage things
like internal requests, software development bugs, customer
complaints, project troubles, material breakdowns, etc.
- The Odoo issues tacker allows you to efficiently manage things
+ The Odoo issues tracker allows you to efficiently manage things
like internal requests, software development bugs, customer
complaints, project troubles, material breakdowns, etc.
- The Odoo issues tacker allows you to efficiently manage things
+ The Odoo issues tracker allows you to efficiently manage things
like internal requests, software development bugs, customer
complaints, project troubles, material breakdowns, etc.
@@ -286,14 +283,10 @@
Click to create an issue.
- You can track your issues from this menu and the action we
- will take.
+ You can track your issues from this menu and the action we will take.
+
+
+
From ccfea6b2f614e55c14d8dd3c2ade78d5b91906ec Mon Sep 17 00:00:00 2001
From: David Monjoie
Date: Thu, 21 Apr 2016 12:40:10 +0200
Subject: [PATCH 15/20] [REM] project_issue: removed dead on_change_template
override
This function was removed in 9.0 at 1dbccc8215d6efe70f3e455a52d04ddb06eba1e3.
---
addons/project_issue/models/account_analytic_account.py | 8 --------
1 file changed, 8 deletions(-)
diff --git a/addons/project_issue/models/account_analytic_account.py b/addons/project_issue/models/account_analytic_account.py
index e6de19fe2b7..76d9439abce 100644
--- a/addons/project_issue/models/account_analytic_account.py
+++ b/addons/project_issue/models/account_analytic_account.py
@@ -10,14 +10,6 @@ class AccountAnalyticAccount(models.Model):
use_issues = fields.Boolean(string='Use Issues', help="Check this box to manage customer activities through this project")
- @api.multi
- def on_change_template(self, template_id, date_start=False):
- res = super(AccountAnalyticAccount, self).on_change_template(template_id, date_start=date_start)
- if template_id and 'value' in res:
- account_analytic_account = self.browse(template_id)
- res['value']['use_issues'] = account_analytic_account.use_issues
- return res
-
@api.multi
def unlink(self):
if self.env['project.issue'].search_count([('project_id.analytic_account_id', 'in', self.ids)]):
From 42b498dc6f6bfd1d375c854d8b415295fbc84e65 Mon Sep 17 00:00:00 2001
From: vbh-odoo
Date: Thu, 6 Aug 2015 16:29:42 +0530
Subject: [PATCH 16/20] [MIG] project_issue: converted test cases from yml to
python
---
addons/project_issue/__openerp__.py | 6 ---
addons/project_issue/test/issue_demo.yml | 14 -------
addons/project_issue/test/issue_process.yml | 12 ------
addons/project_issue/test/issue_users.yml | 28 --------------
addons/project_issue/test/subscribe_issue.yml | 16 --------
addons/project_issue/tests/__init__.py | 6 +++
addons/project_issue/tests/common.py | 21 +++++++++++
.../project_issue/{test => tests}/issue.eml | 0
addons/project_issue/tests/test_issue_demo.py | 18 +++++++++
.../project_issue/tests/test_issue_process.py | 37 +++++++++++++++++++
.../tests/test_subscribe_issue.py | 19 ++++++++++
11 files changed, 101 insertions(+), 76 deletions(-)
delete mode 100644 addons/project_issue/test/issue_demo.yml
delete mode 100644 addons/project_issue/test/issue_process.yml
delete mode 100644 addons/project_issue/test/issue_users.yml
delete mode 100644 addons/project_issue/test/subscribe_issue.yml
create mode 100644 addons/project_issue/tests/__init__.py
create mode 100644 addons/project_issue/tests/common.py
rename addons/project_issue/{test => tests}/issue.eml (100%)
create mode 100644 addons/project_issue/tests/test_issue_demo.py
create mode 100644 addons/project_issue/tests/test_issue_process.py
create mode 100644 addons/project_issue/tests/test_subscribe_issue.py
diff --git a/addons/project_issue/__openerp__.py b/addons/project_issue/__openerp__.py
index d003b39a9dd..7c62d6b7d4c 100644
--- a/addons/project_issue/__openerp__.py
+++ b/addons/project_issue/__openerp__.py
@@ -32,11 +32,5 @@ It allows the manager to quickly check the issues, assign them and decide on the
'views/project_dashboard.xml',
],
'demo': ['data/project_issue_demo.xml'],
- 'test': [
- 'test/issue_users.yml',
- 'test/subscribe_issue.yml',
- 'test/issue_process.yml',
- 'test/issue_demo.yml'
- ],
'application': True,
}
diff --git a/addons/project_issue/test/issue_demo.yml b/addons/project_issue/test/issue_demo.yml
deleted file mode 100644
index f84c716baeb..00000000000
--- a/addons/project_issue/test/issue_demo.yml
+++ /dev/null
@@ -1,14 +0,0 @@
--
- Test the whole create project issue with project manager.
--
- !context
- uid: 'res_users_project_issue_manager'
--
- !record {model: project.issue, id: project_task_1, view: False}:
- task_id: 'project.project_task_17'
- name: 'Error in account module'
--
- !record {model: project.issue, id: project01, view: False}:
- project_id: 'project.project_project_2'
- name: 'OpenERP Integration'
-
diff --git a/addons/project_issue/test/issue_process.yml b/addons/project_issue/test/issue_process.yml
deleted file mode 100644
index 1bc1633688c..00000000000
--- a/addons/project_issue/test/issue_process.yml
+++ /dev/null
@@ -1,12 +0,0 @@
--
- I send mail to get more details. TODO revert mail.mail to mail.compose.message (conversion to customer should be automatic).
--
- !python {model: mail.mail }: |
- ctx = context.copy()
- ctx.update({'active_model': 'project.issue', 'active_id': ref("crm_case_buginaccountsmodule0"), 'active_ids': [ref("crm_case_buginaccountsmodule0")]})
- vals = self.default_get(cr, uid , [], context=ctx)
- try:
- new_id = self.create(cr, uid, {'email_from': 'support@mycompany.com','email_to': 'Robert_Adersen@yahoo.com', 'subject': 'Regarding error in account module we nees more details'})
- self.send_mail(cr, uid, [new_id], context=ctx)
- except Exception, e:
- pass
diff --git a/addons/project_issue/test/issue_users.yml b/addons/project_issue/test/issue_users.yml
deleted file mode 100644
index a0ad327b86b..00000000000
--- a/addons/project_issue/test/issue_users.yml
+++ /dev/null
@@ -1,28 +0,0 @@
--
- Create a user as 'Project manager'
--
- !record {model: res.users, id: res_users_project_issue_manager, view: False}:
- company_id: base.main_company
- name: Project Manager
- login: prim
- email: issuemanager@yourcompany.com
--
- I added groups for Project manager.
--
- !record {model: res.users, id: res_users_project_issue_manager}:
- groups_id:
- - project.group_project_manager
--
- Create a user as 'Project user'
--
- !record {model: res.users, id: res_users_project_issue_user, view: False}:
- company_id: base.main_company
- name: Project User
- login: priu
- email: issueuser@yourcompany.com
--
- I added groups for Project user.
--
- !record {model: res.users, id: res_users_project_issue_user}:
- groups_id:
- - project.group_project_user
diff --git a/addons/project_issue/test/subscribe_issue.yml b/addons/project_issue/test/subscribe_issue.yml
deleted file mode 100644
index a3645feb156..00000000000
--- a/addons/project_issue/test/subscribe_issue.yml
+++ /dev/null
@@ -1,16 +0,0 @@
--
- In Order to test process of Issue in OpenERP, Customer send the issue by email.
--
- !python {model: mail.thread}: |
- request_file = open(openerp.modules.module.get_module_resource('project_issue','test', 'issue.eml'),'rb')
- request_message = request_file.read()
- self.message_process(cr, uid, 'project.issue', request_message)
--
- After getting the mail, I check details of new issue of that customer.
--
- !python {model: project.issue}: |
- issue_ids = self.search(cr, uid, [('email_from', '=', 'Robert Adersen ')])
- assert issue_ids and len(issue_ids), "issue is not created after getting request"
- issue = self.browse(cr, uid, issue_ids[0], context=context)
- assert not issue.partner_id, "Customer should be a new"
- assert issue.name == "Error in the account module", "Subject does not match"
diff --git a/addons/project_issue/tests/__init__.py b/addons/project_issue/tests/__init__.py
new file mode 100644
index 00000000000..423b64bf971
--- /dev/null
+++ b/addons/project_issue/tests/__init__.py
@@ -0,0 +1,6 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+import test_subscribe_issue
+import test_issue_process
+import test_issue_demo
diff --git a/addons/project_issue/tests/common.py b/addons/project_issue/tests/common.py
new file mode 100644
index 00000000000..c5a1654188f
--- /dev/null
+++ b/addons/project_issue/tests/common.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+
+from odoo.tests.common import TransactionCase
+
+
+class TestIssueUsers(TransactionCase):
+ """Tests for unit of res users"""
+
+ def setUp(self):
+ super(TestIssueUsers, self).setUp()
+ self.ProjectIssue = self.env["project.issue"]
+
+ # Created a user as 'Project Manager'
+ # Added group for Project Manager.
+ self.project_manager = self.env["res.users"].create({
+ 'name': 'Project Manager',
+ 'login': 'prim',
+ 'password': 'prim',
+ 'email': 'issuemanager@yourcompany.com',
+ 'groups_id': [(6, 0, [self.ref('project.group_project_manager')])]
+ })
diff --git a/addons/project_issue/test/issue.eml b/addons/project_issue/tests/issue.eml
similarity index 100%
rename from addons/project_issue/test/issue.eml
rename to addons/project_issue/tests/issue.eml
diff --git a/addons/project_issue/tests/test_issue_demo.py b/addons/project_issue/tests/test_issue_demo.py
new file mode 100644
index 00000000000..21846a17351
--- /dev/null
+++ b/addons/project_issue/tests/test_issue_demo.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from odoo.addons.project_issue.tests.common import TestIssueUsers
+
+
+class TestIssueDemo(TestIssueUsers):
+
+ def test_issue_demo(self):
+ # Test the whole create project issue with project manager.
+ self.ProjectIssue.sudo(self.project_manager.id).create({
+ 'name': 'Error in account module',
+ 'task_id': self.ref('project.project_task_17')
+ })
+
+ self.ProjectIssue.sudo(self.project_manager.id).create({
+ 'name': 'Odoo Integration',
+ 'project_id': self.ref('project.project_project_2')
+ })
diff --git a/addons/project_issue/tests/test_issue_process.py b/addons/project_issue/tests/test_issue_process.py
new file mode 100644
index 00000000000..eeedfba39a4
--- /dev/null
+++ b/addons/project_issue/tests/test_issue_process.py
@@ -0,0 +1,37 @@
+# -*- coding: utf-8 -*-
+
+from odoo.addons.project_issue.tests.common import TestIssueUsers
+
+
+class TestIssueProcess(TestIssueUsers):
+
+ def test_issue_process(self):
+ # Sending mail to get more details.
+
+ vals = {'email_from': 'support@mycompany.com',
+ 'email_to': 'Robert_Adersen@yahoo.com',
+ 'subject': 'We need more details regarding your issue in HR module',
+ 'body_html': """
+
+ Hello Mr. Adersen,
+
+
+ We need more details about your issue in the HR module. Could you please
+ send us complete details about the error eg. error message, traceback
+ or what operations you were doing when you the error occured ?
+