[MIG] project_issue: migration to new api

This commit is contained in:
vbh-odoo
2016-06-30 14:41:24 +02:00
committed by David Monjoie
parent 3eb717baec
commit e70605f2d1
19 changed files with 469 additions and 643 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
# -*- encoding: utf-8 -*-
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import models
+6 -8
View File
@@ -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,
}
@@ -1,9 +1,9 @@
<?xml version="1.0"?>
<openerp>
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<!-- notify all employees of module installation -->
<record model="mail.message" id="module_install_notification">
<record id="module_install_notification" model="mail.message">
<field name="model">mail.channel</field>
<field name="res_id" ref="mail.channel_all_employees"/>
<field name="message_type">notification</field>
@@ -73,6 +73,5 @@ Access all issues from the top Project menu, and access the issues of a specific
<field name="parent_id" eval="ref('mt_issue_stage')"/>
<field name="relation_field">project_id</field>
</record>
</data>
</openerp>
</odoo>
@@ -1,7 +1,5 @@
<?xml version="1.0"?>
<openerp>
<data>
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="project.project_project_1" model="project.project">
<field name="use_issues" eval="True"/>
</record>
@@ -21,7 +19,6 @@
<field name="use_issues" eval="True"/>
</record>
</data>
<data noupdate="1">
<!-- Categories -->
@@ -37,13 +34,11 @@
<!-- Issues -->
<record id="crm_case_buginaccountsmodule0" model="project.issue">
<field eval="time.strftime('%Y-%m-08 10:15:00')" name="date"/>
<field name="priority">0</field>
<field name="date" eval="time.strftime('%Y-%m-08 10:15:00')"/>
<field name="user_id" ref="base.user_root"/>
<field name="partner_id" ref="portal.partner_demo_portal"/>
<field eval="1" name="active"/>
<field name="project_id" ref="project.project_project_1"/>
<field eval="15.0" name="duration"/>
<field name="duration" eval="15.0"/>
<field name="name">Bug in Accounts module</field>
<field name="email_from">agr@agrolait.com</field>
<field name="stage_id" ref="project.project_stage_1"/>
@@ -52,23 +47,20 @@
</record>
<record id="crm_case_programnotgivingproperoutput0" model="project.issue">
<field eval="time.strftime('%Y-%m-15 12:50:00')" name="date"/>
<field name="date" eval="time.strftime('%Y-%m-15 12:50:00')"/>
<field name="priority">1</field>
<field name="user_id" ref="base.user_root"/>
<field name="partner_id" ref="base.res_partner_1"/>
<field eval="1" name="active"/>
<field eval="3.5" name="duration"/>
<field name="duration" eval="3.5"/>
<field name="name">Program not giving proper output</field>
<field name="project_id" ref="project.project_project_4"/>
<field name="stage_id" ref="project.project_stage_1"/>
</record>
<record id="crm_case_outputincorrect0" model="project.issue">
<field eval="time.strftime('%Y-%m-18 14:30:00')" name="date"/>
<field name="priority">0</field>
<field name="date" eval="time.strftime('%Y-%m-18 14:30:00')"/>
<field name="user_id" ref="base.user_demo"/>
<field eval="1" name="active"/>
<field eval="2.3" name="duration"/>
<field name="duration" eval="2.3"/>
<field name="project_id" ref="project.project_project_5"/>
<field name="name">Output incorrect</field>
<field name="stage_id" ref="project.project_stage_2"/>
@@ -78,36 +70,33 @@
</record>
<record id="crm_case_problemloadingpage0" model="project.issue">
<field eval="time.strftime('%Y-%m-20 15:25:05')" name="date"/>
<field name="date" eval="time.strftime('%Y-%m-20 15:25:05')"/>
<field name="priority">1</field>
<field name="user_id" ref="base.user_root"/>
<field name="partner_id" ref="base.res_partner_4"/>
<field eval="1" name="active"/>
<field eval="4.0" name="duration"/>
<field name="duration" eval="4.0"/>
<field name="project_id" ref="project.project_project_4"/>
<field name="name">Problem loading page</field>
<field name="stage_id" ref="project.project_stage_0"/>
</record>
<record id="crm_case_pagenotfound0" model="project.issue">
<field eval="time.strftime('%Y-%m-22 18:15:00')" name="date"/>
<field name="date" eval="time.strftime('%Y-%m-22 18:15:00')"/>
<field name="priority">1</field>
<field name="user_id" ref="base.user_root"/>
<field name="partner_id" ref="base.res_partner_2"/>
<field eval="1" name="active"/>
<field eval="1.0" name="duration"/>
<field name="duration" eval="1.0"/>
<field name="project_id" ref="project.project_project_4"/>
<field name="name">Page not Found</field>
<field name="stage_id" ref="project.project_stage_0"/>
</record>
<record id="crm_case_programmingerror0" model="project.issue">
<field eval="time.strftime('%Y-%m-24 09:45:00')" name="date"/>
<field name="date" eval="time.strftime('%Y-%m-24 09:45:00')"/>
<field name="priority">1</field>
<field name="user_id" ref="base.user_root"/>
<field name="partner_id" ref="base.res_partner_3"/>
<field eval="1" name="active"/>
<field eval="4.0" name="duration"/>
<field name="duration" eval="4.0"/>
<field name="project_id" ref="project.project_project_4"/>
<field name="name">Programming Error</field>
<field name="stage_id" ref="project.project_stage_0"/>
@@ -117,72 +106,65 @@
</record>
<record id="crm_case_logicalerrorinprogram0" model="project.issue">
<field eval="time.strftime('%Y-%m-26 11:10:00')" name="date"/>
<field name="date" eval="time.strftime('%Y-%m-26 11:10:00')"/>
<field name="priority">2</field>
<field name="user_id" ref="base.user_root"/>
<field name="partner_id" ref="base.res_partner_1"/>
<field eval="1" name="active"/>
<field eval="2.0" name="duration"/>
<field name="duration" eval="2.0"/>
<field name="project_id" ref="project.project_project_2"/>
<field name="name">Logical Error in Program</field>
<field name="stage_id" ref="project.project_stage_3"/>
</record>
<record id="crm_case_constrainterror0" model="project.issue">
<field eval="time.strftime('%Y-%m-25 13:35:00')" name="date"/>
<field name="date" eval="time.strftime('%Y-%m-25 13:35:00')"/>
<field name="priority">2</field>
<field name="user_id" ref="base.user_root"/>
<field name="partner_id" ref="base.res_partner_1"/>
<field eval="1" name="active"/>
<field eval="7.3" name="duration"/>
<field name="duration" eval="7.3"/>
<field name="project_id" ref="project.project_project_2"/>
<field name="name">Constraint Error</field>
<field name="stage_id" ref="project.project_stage_1"/>
</record>
<record id="crm_case_errorinprogram0" model="project.issue">
<field eval="time.strftime('%Y-%m-28 15:40:00')" name="date"/>
<field name="date" eval="time.strftime('%Y-%m-28 15:40:00')"/>
<field name="priority">2</field>
<field name="user_id" ref="base.user_demo"/>
<field name="partner_id" ref="base.res_partner_3"/>
<field eval="1" name="active"/>
<field eval="1.3" name="duration"/>
<field name="duration" eval="1.3"/>
<field name="project_id" ref="project.project_project_4"/>
<field name="name">Error in Program</field>
<field name="stage_id" ref="project.project_stage_1"/>
</record>
<record id="crm_case_patcheserrorinprogram0" model="project.issue">
<field eval="time.strftime('%Y-%m-28 16:30:00')" name="date"/>
<field name="date" eval="time.strftime('%Y-%m-28 16:30:00')"/>
<field name="priority">2</field>
<field name="user_id" ref="base.user_root"/>
<field name="partner_id" ref="base.res_partner_2"/>
<field eval="1" name="active"/>
<field eval="13.0" name="duration"/>
<field name="duration" eval="13.0"/>
<field name="project_id" ref="project.project_project_2"/>
<field name="name">Patches Error in Program</field>
<field name="stage_id" ref="project.project_stage_1"/>
</record>
<record id="crm_case_newfeaturestobeadded0" model="project.issue">
<field eval="time.strftime('%Y-%m-01 12:15:10')" name="date"/>
<field name="priority">0</field>
<field name="date" eval="time.strftime('%Y-%m-01 12:15:10')"/>
<field name="user_id" ref="base.user_root"/>
<field name="partner_id" ref="base.res_partner_12"/>
<field eval="1" name="active"/>
<field eval="3.2" name="duration"/>
<field name="duration" eval="3.2"/>
<field name="project_id" ref="project.project_project_3"/>
<field name="name">New Features To Be Added</field>
<field name="stage_id" ref="project.project_stage_1"/>
</record>
<record id="crm_case_addmenustothemodule0" model="project.issue">
<field eval="time.strftime('%Y-%m-05 18:00:00')" name="date"/>
<field name="date" eval="time.strftime('%Y-%m-05 18:00:00')"/>
<field name="priority">1</field>
<field name="user_id" ref="base.user_demo"/>
<field name="partner_id" ref="base.res_partner_12"/>
<field eval="1" name="active"/>
<field eval="3.0" name="duration"/>
<field name="duration" eval="3.0"/>
<field name="project_id" ref="project.project_project_3"/>
<field name="name">Add menus to the module</field>
<field name="email_from">info@opensides.be</field>
@@ -190,12 +172,11 @@
</record>
<record id="crm_case_includeattendancesheetinproject0" model="project.issue">
<field eval="time.strftime('%Y-%m-10 17:05:30')" name="date"/>
<field name="date" eval="time.strftime('%Y-%m-10 17:05:30')"/>
<field name="priority">1</field>
<field name="user_id" ref="base.user_root"/>
<field name="partner_id" ref="base.res_partner_10"/>
<field eval="1" name="active"/>
<field eval="2.0" name="duration"/>
<field name="duration" eval="2.0"/>
<field name="project_id" ref="project.project_project_2"/>
<field name="name">Include Attendance sheet in Project</field>
<field name="email_from">contact@tecsas.fr</field>
@@ -203,44 +184,41 @@
</record>
<record id="crm_case_createnewobject0" model="project.issue">
<field eval="time.strftime('%Y-%m-15 10:35:15')" name="date"/>
<field name="date" eval="time.strftime('%Y-%m-15 10:35:15')"/>
<field name="priority">1</field>
<field name="user_id" ref="base.user_root"/>
<field name="partner_id" ref="base.res_partner_1"/>
<field eval="1" name="active"/>
<field eval="2.45" name="duration"/>
<field name="duration" eval="2.45"/>
<field name="project_id" ref="project.project_project_4"/>
<field name="name">Create new object</field>
<field name="stage_id" ref="project.project_stage_0"/>
</record>
<record id="crm_case_improvereportsinhrms0" model="project.issue">
<field eval="time.strftime('%Y-%m-19 12:15:00')" name="date"/>
<field name="priority">0</field>
<field name="date" eval="time.strftime('%Y-%m-19 12:15:00')"/>
<field name="user_id" ref="base.user_root"/>
<field name="partner_id" ref="base.res_partner_3"/>
<field eval="1" name="active"/>
<field eval="15.0" name="duration"/>
<field name="duration" eval="15.0"/>
<field name="project_id" ref="project.project_project_4"/>
<field name="name">Improve Reports in HRMS</field>
<field name="stage_id" ref="project.project_stage_2"/>
</record>
<record id="crm_case_improvereportsinpms0" model="project.issue">
<field eval="time.strftime('%Y-%m-21 14:30:00')" name="date"/>
<field name="date" eval="time.strftime('%Y-%m-21 14:30:00')"/>
<field name="priority">2</field>
<field name="user_id" ref="base.user_demo"/>
<field name="partner_id" ref="base.res_partner_3"/>
<field eval="1" name="active"/>
<field eval="06.15" name="duration"/>
<field name="duration" eval="06.15"/>
<field name="project_id" ref="project.project_project_4"/>
<field name="name">Improve Reports in PMS</field>
<field name="stage_id" ref="project.project_stage_1"/>
</record>
<!-- Call function to set some issues as 'unread' -->
<!-- <function model="project.issue" name="message_mark_as_unread"
eval="[ ref('crm_case_improvereportsinhrms0'), ref('crm_case_createnewobject0')], {}"/> -->
</data>
</openerp>
</odoo>
+1 -1
View File
@@ -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
@@ -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)
+237 -318
View File
@@ -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
+25 -52
View File
@@ -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')
@@ -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):
+8 -13
View File
@@ -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])])
@@ -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')
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<odoo>
<record id="view_project_issue_report_pivot" model="ir.ui.view">
<field name="name">project.issue.report.pivot</field>
<field name="model">project.issue.report</field>
@@ -93,9 +92,6 @@
<field name="help">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.</field>
</record>
<menuitem action="action_project_issue_report" id="menu_project_issue_report_tree"
<menuitem id="menu_project_issue_report_tree" action="action_project_issue_report"
parent="base.menu_project_report"/>
</data>
</openerp>
</odoo>
@@ -1,15 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<odoo>
<data noupdate="1">
<record model="ir.rule" id="issue_project_manager_rule">
<record id="issue_project_manager_rule" model="ir.rule">
<field name="name">Project/Issue: project manager: see all</field>
<field name="model_id" ref="model_project_issue"/>
<field name="domain_force">[(1, '=', 1)]</field>
<field name="groups" eval="[(4,ref('project.group_project_manager'))]"/>
</record>
<record model="ir.rule" id="issue_user_rule">
<record id="issue_user_rule" model="ir.rule">
<field name="name">Project/Issue: employees: employee or (followers and following)</field>
<field name="model_id" ref="model_project_issue"/>
<field name="domain_force">['|',
@@ -46,4 +46,4 @@
</record>
</data>
</openerp>
</odoo>
@@ -1,17 +1,15 @@
<?xml version="1.0"?>
<openerp>
<data>
<record id="analytic_account_inherited_issue_form" model="ir.ui.view">
<field name="name">account.analytic.account.issue.form.inherit</field>
<field name="model">account.analytic.account</field>
<field name="inherit_id" ref="project.analytic_account_inherited_form"/>
<field eval="18" name="priority"/>
<field name="arch" type="xml">
<xpath expr='//div[@name="project"]' position='inside'>
<field name="use_issues" groups="base.group_no_one"/>
<label for="use_issues" groups="base.group_no_one"/>
</xpath>
</field>
</record>
</data>
</openerp>
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="analytic_account_inherited_issue_form" model="ir.ui.view">
<field name="name">account.analytic.account.issue.form.inherit</field>
<field name="model">account.analytic.account</field>
<field name="inherit_id" ref="project.analytic_account_inherited_form"/>
<field name="priority" eval="18"/>
<field name="arch" type="xml">
<xpath expr='//div[@name="project"]' position='inside'>
<field name="use_issues" groups="base.group_no_one"/>
<label for="use_issues" groups="base.group_no_one"/>
</xpath>
</field>
</record>
</odoo>
@@ -1,3 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_project_kanban_inherited" model="ir.ui.view">
<field name="name">project.project.kanban.inherited</field>
@@ -1,11 +1,9 @@
<?xml version="1.0"?>
<openerp>
<data>
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!--
ALL BUGS
-->
<record model="ir.actions.act_window" id="project_issue_categ_act0">
<record id="project_issue_categ_act0" model="ir.actions.act_window">
<field name="name">Issues</field>
<field name="res_model">project.issue</field>
<field name="view_type">form</field>
@@ -16,49 +14,48 @@
<field name="search_view_id" ref="view_project_issue_filter"/>
<field name="help" type="html">
<p>
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.
</p>
</field>
</record>
<record model="ir.actions.act_window.view" id="action_crm_tag_kanban_view0">
<record id="action_crm_tag_kanban_view0" model="ir.actions.act_window.view">
<field name="sequence" eval="0"/>
<field name="view_mode">kanban</field>
<field name="view_id" ref="project_issue_kanban_view"/>
<field name="act_window_id" ref="project_issue_categ_act0"/>
</record>
<record model="ir.actions.act_window.view" id="action_crm_tag_tree_view0">
<record id="action_crm_tag_tree_view0" model="ir.actions.act_window.view">
<field name="sequence" eval="1"/>
<field name="view_mode">tree</field>
<field name="view_id" ref="project_issue_tree_view"/>
<field name="act_window_id" ref="project_issue_categ_act0"/>
</record>
<record model="ir.actions.act_window.view" id="action_crm_tag_calendar_view0">
<record id="action_crm_tag_calendar_view0" model="ir.actions.act_window.view">
<field name="sequence" eval="2"/>
<field name="view_mode">calendar</field>
<field name="view_id" ref="project_issue_calendar_view"/>
<field name="act_window_id" ref="project_issue_categ_act0"/>
</record>
<record model="ir.actions.act_window.view" id="action_crm_tag_form_view0">
<record id="action_crm_tag_form_view0" model="ir.actions.act_window.view">
<field name="sequence" eval="3"/>
<field name="view_mode">form</field>
<field name="view_id" ref="project_issue_form_view"/>
<field name="act_window_id" ref="project_issue_categ_act0"/>
</record>
<record model="ir.actions.act_window.view" id="action_crm_tag_graph_view0">
<record id="action_crm_tag_graph_view0" model="ir.actions.act_window.view">
<field name="sequence" eval="4"/>
<field name="view_mode">graph</field>
<field name="view_id" ref="project_issue_graph_view"/>
<field name="act_window_id" ref="project_issue_categ_act0"/>
</record>
<menuitem name="Issues" id="menu_project_issue_track" parent="project.menu_project_management"
<menuitem id="menu_project_issue_track" name="Issues" parent="project.menu_project_management"
action="project_issue_categ_act0" sequence="15"/>
</data>
</openerp>
</odoo>
@@ -1,9 +1,7 @@
<?xml version="1.0"?>
<openerp>
<data>
<record model="ir.ui.view" id="project_issue_form_view">
<field name="name">Project Issue Tracker Form</field>
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="project_issue_form_view" model="ir.ui.view">
<field name="name">project.issue.form.view</field>
<field name="model">project.issue</field>
<field name="arch" type="xml">
<form>
@@ -30,11 +28,10 @@
<field name="tag_ids" widget="many2many_tags"/>
</group>
<group>
<field name="partner_id" on_change="onchange_partner_id(partner_id)"/>
<field name="partner_id"/>
<field name="email_from"/>
<field name="project_id" on_change="on_change_project(project_id)" context="{'default_use_issues':1}"
groups="base.group_user"/>
<field name="task_id" groups="base.group_no_one" on_change="onchange_task_id(task_id)" context="{'default_project_id':project_id}"/>
<field name="project_id" context="{'default_use_issues':1}" groups="base.group_user"/>
<field name="task_id" groups="base.group_no_one" context="{'default_project_id':project_id}"/>
</group>
</group>
<notebook>
@@ -67,8 +64,8 @@
</field>
</record>
<record model="ir.ui.view" id="project_issue_tree_view">
<field name="name">Project Issue Tracker Tree</field>
<record id="project_issue_tree_view" model="ir.ui.view">
<field name="name">project.issue.tree.view</field>
<field name="model">project.issue</field>
<field name="arch" type="xml">
<tree string="Issue Tracker Tree" decoration-bf="message_needaction==True">
@@ -78,7 +75,7 @@
<field name="partner_id"/>
<field name="project_id" groups="base.group_user"/>
<field name="priority" string="Priority" groups="base.group_user"/>
<field name="create_date"/>
<field name="create_date" string="Creation Date"/>
<field name="user_id"/>
<field name="stage_id" widget="selection" readonly="1"/>
<field name="tag_ids" invisible="1"/>
@@ -88,7 +85,7 @@
</record>
<record id="view_project_issue_filter" model="ir.ui.view">
<field name="name">Project Issue Tracker Search</field>
<field name="name">project.issue.search.view</field>
<field name="model">project.issue</field>
<field name="arch" type="xml">
<search string="Issue Tracker Search">
@@ -121,8 +118,8 @@
</field>
</record>
<record model="ir.ui.view" id="project_issue_calendar_view">
<field name="name">Project Issue Tracker Calendar</field>
<record id="project_issue_calendar_view" model="ir.ui.view">
<field name="name">project.issue.calendar.view</field>
<field name="model">project.issue</field>
<field name="priority" eval="2"/>
<field name="arch" type="xml">
@@ -136,8 +133,8 @@
<!-- Project Issue Karban View-->
<record model="ir.ui.view" id="project_issue_kanban_view">
<field name="name">Project Issue Kanban</field>
<record id="project_issue_kanban_view" model="ir.ui.view">
<field name="name">project.issue.kanban.view</field>
<field name="model">project.issue</field>
<field name="arch" type="xml">
<kanban default_group_by="stage_id" class="o_kanban_small_column">
@@ -198,8 +195,8 @@
</field>
</record>
<record model="ir.ui.view" id="project_issue_pivot_view">
<field name="name">Project Issue Tracker Pivot</field>
<record id="project_issue_pivot_view" model="ir.ui.view">
<field name="name">project.issue.pivot.view</field>
<field name="model">project.issue</field>
<field name="arch" type="xml">
<pivot string="Project Issues">
@@ -209,8 +206,8 @@
</field>
</record>
<record model="ir.ui.view" id="project_issue_graph_view">
<field name="name">Project Issue Tracker Graph</field>
<record id="project_issue_graph_view" model="ir.ui.view">
<field name="name">project.issue.graph.view</field>
<field name="model">project.issue</field>
<field name="arch" type="xml">
<graph string="Project Issues" type="bar">
@@ -222,8 +219,8 @@
<!-- Feature Requests -->
<record model="ir.ui.view" id="project_feature_tree_view">
<field name="name">Project Issue- Feature Tracker Tree</field>
<record id="project_feature_tree_view" model="ir.ui.view">
<field name="name">project.issue.tree.view.featured</field>
<field name="model">project.issue</field>
<field name="arch" type="xml">
<tree string="Feature Tracker Tree" decoration-bf="message_needaction==True">
@@ -250,7 +247,7 @@
</field>
<field name="help" type="html">
<p>
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.
</p>
@@ -264,7 +261,7 @@
<field name="view_mode">kanban,tree,form,calendar,graph</field>
<field name="help" type="html">
<p>
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.
</p>
@@ -286,14 +283,10 @@
<p class="oe_view_nocontent_create">
Click to create an issue.
</p><p>
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.
</p>
</field>
</record>
<menuitem name="Issues" id="portal_services_issues" parent="portal.portal_projects"
action="open_view_issue" sequence="10"/>
</data>
</openerp>
<menuitem id="portal_services_issues" name="Issues" parent="portal.portal_projects"
action="open_view_issue" sequence="10"/>
</odoo>
@@ -1,36 +1,28 @@
<?xml version="1.0"?>
<openerp>
<data>
<record id="view_project_form_inherited" model="ir.ui.view">
<field name="name">project.project.form.inherited</field>
<field name="model">project.project</field>
<field name="inherit_id" ref="project.edit_project"/>
<field name="arch" type="xml">
<xpath expr='//field[@name="use_tasks"]' position='attributes'>
<attribute name="on_change">on_change_use_tasks_or_issues(use_tasks, use_issues)</attribute>
</xpath>
<xpath expr='//div[@name="options_active"]' position="attributes">
<attribute name="invisible">0</attribute>
</xpath>
<xpath expr='//div[@name="options_active"]' position='inside'>
<div>
<field name="use_issues" class="oe_inline"
on_change="on_change_use_tasks_or_issues(use_tasks, use_issues)"/>
<label for="use_issues" class="oe_inline" string="Issues"/>
<span attrs="{'invisible':[('use_issues', '=', False)]}">as </span>
<field name="label_issues" class="oe_inline oe_input_align" attrs="{'invisible': [('use_issues', '=', False)]}"/>
</div>
</xpath>
<div name="button_box" position="inside">
<button class="oe_stat_button" type="action" attrs="{'invisible':[('use_issues','=', False)]}"
name="%(act_project_project_2_project_issue_all)d" icon="fa-bug">
<field string="Issues" name="issue_count" widget="statinfo" options="{'label_field': 'label_issues'}"/>
</button>
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Project -->
<record id="view_project_form_inherited" model="ir.ui.view">
<field name="name">project.project.form.inherited</field>
<field name="model">project.project</field>
<field name="inherit_id" ref="project.edit_project"/>
<field name="arch" type="xml">
<xpath expr='//div[@name="options_active"]' position="attributes">
<attribute name="invisible">0</attribute>
</xpath>
<xpath expr='//div[@name="options_active"]' position='inside'>
<div>
<field name="use_issues" class="oe_inline"/>
<label for="use_issues" class="oe_inline" string="Issues"/>
<span attrs="{'invisible':[('use_issues', '=', False)]}">as </span>
<field name="label_issues" class="oe_inline oe_input_align" attrs="{'invisible': [('use_issues', '=', False)]}"/>
</div>
<xpath expr='//field[@name="use_tasks"]' position="attributes">
<attribute name="attrs">{'on_change': 'on_change_use_tasks_or_issues(use_tasks, use_issues)'}</attribute>
</xpath>
</field>
</record>
</data>
</openerp>
</xpath>
<div name="button_box" position="inside">
<button class="oe_stat_button" type="action" attrs="{'invisible':[('use_issues','=', False)]}"
name="%(act_project_project_2_project_issue_all)d" icon="fa-bug">
<field string="Issues" name="issue_count" widget="statinfo" options="{'label_field': 'label_issues'}"/>
</button>
</div>
</field>
</record>
</odoo>
+21 -24
View File
@@ -1,24 +1,21 @@
<?xml version="1.0"?>
<openerp>
<data>
<!-- res.partner -->
<record model="ir.ui.view" id="res_partner_issues_button_view">
<field name="name">res.partner.issues.button.view</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form" />
<field name="priority" eval="50"/>
<field name="groups_id" eval="[(4, ref('project.group_project_user'))]"/>
<field name="arch" type="xml">
<div name="button_box" position="inside">
<button class="oe_stat_button" type="action" name="%(action_view_issues)d"
context="{'search_default_partner_id': active_id, 'default_partner_id': active_id}"
attrs="{'invisible': [('customer', '=', False)]}"
icon="fa-bug">
<field string="Issues" name="issue_count" widget="statinfo"/>
</button>
</div>
</field>
</record>
</data>
</openerp>
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- res.partner -->
<record id="res_partner_issues_button_view" model="ir.ui.view">
<field name="name">res.partner.issues.button.view.inherit</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form" />
<field name="priority" eval="50"/>
<field name="groups_id" eval="[(4, ref('project.group_project_user'))]"/>
<field name="arch" type="xml">
<div name="button_box" position="inside">
<button class="oe_stat_button" type="action" name="%(action_view_issues)d"
context="{'search_default_partner_id': active_id, 'default_partner_id': active_id}"
attrs="{'invisible': [('customer', '=', False)]}"
icon="fa-bug">
<field name="issue_count" string="Issues" widget="statinfo"/>
</button>
</div>
</field>
</record>
</odoo>