diff --git a/addons/crm/__openerp__.py b/addons/crm/__openerp__.py
index 271422a60dd..04feb481db2 100644
--- a/addons/crm/__openerp__.py
+++ b/addons/crm/__openerp__.py
@@ -52,6 +52,7 @@ Dashboard for CRM will include:
'security/crm_security.xml',
'security/ir.model.access.csv',
+ 'wizard/crm_activity_log_views.xml',
'wizard/crm_lead_lost_view.xml',
'wizard/crm_lead_to_opportunity_view.xml',
'wizard/crm_merge_opportunities_view.xml',
diff --git a/addons/crm/crm_lead.py b/addons/crm/crm_lead.py
index 590759ce1ec..8f046fc8cdb 100644
--- a/addons/crm/crm_lead.py
+++ b/addons/crm/crm_lead.py
@@ -127,6 +127,21 @@ class crm_lead(format_address, osv.osv):
'stage_id': _read_group_stage_ids
}
+ def _compute_kanban_state(self, cr, uid, ids, fields, args, context=None):
+ """ Very interesting kanban state color. This makes complete sense. Or
+ not. """
+ result = {}
+ today = datetime.now()
+ for lead in self.browse(cr, uid, ids, context=context):
+ result[lead.id] = 'normal'
+ if lead.date_action:
+ lead_date = datetime.strptime(lead.date_action, tools.DEFAULT_SERVER_DATE_FORMAT)
+ if lead_date > today:
+ result[lead.id] = 'green'
+ elif lead_date < today:
+ result[lead.id] = 'red'
+ return result
+
def _compute_day(self, cr, uid, ids, fields, args, context=None):
"""
:return dict: difference between current date and log date
@@ -151,6 +166,7 @@ class crm_lead(format_address, osv.osv):
duration = abs(int(ans.days))
res[lead.id][field] = duration
return res
+
def _meeting_count(self, cr, uid, ids, field_name, arg, context=None):
Event = self.pool['calendar.event']
return {
@@ -170,6 +186,9 @@ class crm_lead(format_address, osv.osv):
'email_from': fields.char('Email', size=128, help="Email address of the contact", select=1),
'team_id': fields.many2one('crm.team', 'Sales Team', oldname='section_id',
select=True, track_visibility='onchange', help='When sending mails, the default email address is taken from the sales team.'),
+ 'kanban_state': fields.function(
+ _compute_kanban_state, string='Activity State', type="selection",
+ selection=[('grey', 'Normal'), ('red', 'Blocked'), ('green', 'Ready for next stage')]),
'create_date': fields.datetime('Creation Date', readonly=True),
'email_cc': fields.text('Global CC', 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"),
'description': fields.text('Notes'),
@@ -207,11 +226,7 @@ class crm_lead(format_address, osv.osv):
'planned_revenue': fields.float('Expected Revenue', track_visibility='always'),
'date_deadline': fields.date('Expected Closing', help="Estimate of the date on which the opportunity will be won."),
# CRM Actions
- 'last_activity_id': fields.many2one("crm.activity", "Last Activity", select=True),
'next_activity_id': fields.many2one("crm.activity", "Next Activity", select=True),
- 'next_activity_1': fields.related("last_activity_id", "activity_1_id", "name", type="char", string="Next Activity 1"),
- 'next_activity_2': fields.related("last_activity_id", "activity_2_id", "name", type="char", string="Next Activity 2"),
- 'next_activity_3': fields.related("last_activity_id", "activity_3_id", "name", type="char", string="Next Activity 3"),
'date_action': fields.date('Next Activity Date', select=True),
'title_action': fields.char('Next Activity Summary'),
@@ -365,62 +380,9 @@ class crm_lead(format_address, osv.osv):
# Backward compatibility
case_mark_won = action_set_won
- def log_next_activity_1(self, cr, uid, ids, context=None):
- return self.set_next_activity(cr, uid, ids, next_activity_name='activity_1_id', context=context)
-
- def log_next_activity_2(self, cr, uid, ids, context=None):
- return self.set_next_activity(cr, uid, ids, next_activity_name='activity_2_id', context=context)
-
- def log_next_activity_3(self, cr, uid, ids, context=None):
- return self.set_next_activity(cr, uid, ids, next_activity_name='activity_3_id', context=context)
-
- def set_next_activity(self, cr, uid, ids, next_activity_name, context=None):
- for lead in self.browse(cr, uid, ids, context=context):
- if not lead.last_activity_id:
- continue
- next_activity = next_activity_name and getattr(lead.last_activity_id, next_activity_name, False) or False
- if next_activity:
- date_action = False
- if next_activity.days:
- date_action = (datetime.now() + timedelta(days=next_activity.days)).strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT),
- lead.write({
- 'next_activity_id': next_activity.id,
- 'date_action': date_action,
- 'title_action': next_activity.description,
- })
- return True
-
- def log_next_activity_done(self, cr, uid, ids, context=None, next_activity_name=False):
- to_clear_ids = []
- for lead in self.browse(cr, uid, ids, context=context):
- if not lead.next_activity_id:
- continue
- body_html = """
Activity Done: ${object.next_activity_id.name}
-%if object.title_action:
-${object.title_action}
-%endif"""
- body_html = self.pool['mail.template'].render_template(cr, uid, body_html, 'crm.lead', lead.id, context=context)
- msg_id = lead.message_post(body_html, subtype_id=lead.next_activity_id.subtype_id.id)
- to_clear_ids.append(lead.id)
- self.write(cr, uid, [lead.id], {'last_activity_id': lead.next_activity_id.id}, context=context)
-
- if to_clear_ids:
- self.cancel_next_activity(cr, uid, to_clear_ids, context=context)
- return True
-
- def cancel_next_activity(self, cr, uid, ids, context=None):
- return self.write(cr, uid, ids, {
- 'next_activity_id': False,
- 'date_action': False,
- 'title_action': False,
- }, context=context)
-
def onchange_next_activity_id(self, cr, uid, ids, next_activity_id, context=None):
if not next_activity_id:
return {'value': {
- 'next_action1': False,
- 'next_action2': False,
- 'next_action3': False,
'title_action': False,
'date_action': False,
}}
@@ -429,12 +391,8 @@ class crm_lead(format_address, osv.osv):
if activity.days:
date_action = (datetime.now() + timedelta(days=activity.days)).strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)
return {'value': {
- 'next_activity_1': activity.activity_1_id and activity.activity_1_id.name or False,
- 'next_activity_2': activity.activity_2_id and activity.activity_2_id.name or False,
- 'next_activity_3': activity.activity_3_id and activity.activity_3_id.name or False,
'title_action': activity.description,
'date_action': date_action,
- 'last_activity_id': False,
}}
def _merge_get_result_type(self, cr, uid, opps, context=None):
@@ -1234,12 +1192,10 @@ class crm_lead_tag(osv.Model):
class crm_lost_reason(osv.Model):
_name = "crm.lost.reason"
_description = 'Reason for loosing leads'
-
_columns = {
'name': fields.char('Name', required=True),
'active': fields.boolean('Active'),
}
-
_defaults = {
'active': True,
}
diff --git a/addons/crm/crm_lead_view.xml b/addons/crm/crm_lead_view.xml
index 33031741f91..b7ee54d84ae 100644
--- a/addons/crm/crm_lead_view.xml
+++ b/addons/crm/crm_lead_view.xml
@@ -279,12 +279,15 @@
+
+
+
@@ -312,22 +315,22 @@
- -
-
-
- oe_kanban_text_red
-
-
- :
-
-
+ ,
+
@@ -381,10 +384,6 @@
-
-
Opportunities
@@ -400,6 +399,8 @@
type="action" class="oe_highlight"
context="{'default_lead_id': active_id}"
attrs="{'invisible': [('active', '=', False)]}"/>
+
-
-
@@ -457,7 +456,6 @@
-
@@ -468,27 +466,10 @@
on
-
-
- |
-
- |
-
-
-
+
-
-
-
-
+ attrs="{'invisible': [('next_activity_id', '=', False)]}"/>
diff --git a/addons/crm/data/crm_action_demo.xml b/addons/crm/data/crm_action_demo.xml
index 36570cf82db..6c1bb4a25ba 100644
--- a/addons/crm/data/crm_action_demo.xml
+++ b/addons/crm/data/crm_action_demo.xml
@@ -7,32 +7,28 @@
Follow-up quote
30
+
Make Quote
Send a quotation
15
-
+
Call for Demo
10
Call for a demo
-
+
-
5
+
-
-
- Follow-up call
-
-
-
+
15
diff --git a/addons/crm/models/crm_activity.py b/addons/crm/models/crm_activity.py
index b2f211d6fdb..ee6e7c89026 100644
--- a/addons/crm/models/crm_activity.py
+++ b/addons/crm/models/crm_activity.py
@@ -6,12 +6,12 @@ from openerp import api, fields, models
class CrmActivity(models.Model):
''' CrmActivity is a model introduced in Odoo v9 that models activities
- performed in CRM, like phonecalls, sending emails, making demonstrations,
+ performed in CRM, like phone calls, sending emails, making demonstrations,
... Users are able to configure their custom activities.
- Each activity has up to three next activities. This allows to model light
- custom workflows. This way sales manager can configure their crm workflow
- that salepersons will use in their daily job.
+ Each activity can configure recommended next activities. This allows to model
+ light custom workflows. This way sales manager can configure their crm
+ workflow that salepersons will use in their daily job.
CrmActivity inherits from mail.message.subtype. This allows users to follow
some activities through subtypes. Each activity will generate messages with
@@ -29,9 +29,12 @@ class CrmActivity(models.Model):
sequence = fields.Integer('Sequence', default=0)
team_id = fields.Many2one('crm.team', string='Sales Team')
subtype_id = fields.Many2one('mail.message.subtype', string='Message Subtype', required=True, ondelete='cascade')
- activity_1_id = fields.Many2one('crm.activity', string="Next Activity 1")
- activity_2_id = fields.Many2one('crm.activity', string="Next Activity 2")
- activity_3_id = fields.Many2one('crm.activity', string="Next Activity 3")
+ recommended_activity_ids = fields.Many2many(
+ 'crm.activity', 'crm_activity_rel', 'activity_id', 'recommended_id',
+ string='Recommended Next Activities')
+ preceding_activity_ids = fields.Many2many(
+ 'crm.activity', 'crm_activity_rel', 'recommended_id', 'activity_id',
+ string='Preceding Activities')
@api.model
def create(self, values):
diff --git a/addons/crm/views/crm_action_views.xml b/addons/crm/views/crm_action_views.xml
index 1d46749b652..d136ec27696 100644
--- a/addons/crm/views/crm_action_views.xml
+++ b/addons/crm/views/crm_action_views.xml
@@ -27,9 +27,7 @@
-
-
-
+
diff --git a/addons/crm/wizard/__init__.py b/addons/crm/wizard/__init__.py
index e4c563df3a1..7f3fa1754f1 100644
--- a/addons/crm/wizard/__init__.py
+++ b/addons/crm/wizard/__init__.py
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+import crm_activity_log
import crm_lead_lost
import crm_partner_binding
import crm_lead_to_opportunity
diff --git a/addons/crm/wizard/crm_activity_log.py b/addons/crm/wizard/crm_activity_log.py
new file mode 100644
index 00000000000..517993793fa
--- /dev/null
+++ b/addons/crm/wizard/crm_activity_log.py
@@ -0,0 +1,98 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from datetime import datetime, timedelta
+
+from openerp import api, fields, models, tools, _
+
+
+class ActivityLog(models.TransientModel):
+ _name = "crm.activity.log"
+ _description = "Log an Activity"
+ _rec_name = 'title_action'
+
+ next_activity_id = fields.Many2one('crm.activity', 'Activity')
+ last_activity_id = fields.Many2one('crm.activity', 'Previous Activity')
+ recommended_activity_id = fields.Many2one("crm.activity", "Recommended Activities")
+ title_action = fields.Char('Summary')
+ note = fields.Html('Note')
+ date_action = fields.Date('Next Activity Date')
+ lead_id = fields.Many2one(
+ 'crm.lead', 'Lead', required=True,
+ default=lambda self: self._context.get('default_lead_id',
+ self._context.get('active_model') == 'crm.lead' and self._context.get('active_id') or False))
+ team_id = fields.Many2one(
+ 'crm.team', 'Sales Team')
+ date_deadline = fields.Date('Expected Closing')
+ planned_revenue = fields.Float('Expected Revenue')
+
+ @api.onchange('lead_id')
+ def onchange_lead_id(self):
+ self.next_activity_id = self.lead_id.next_activity_id
+ self.date_deadline = self.lead_id.date_deadline
+ self.team_id = self.lead_id.team_id
+ self.planned_revenue = self.lead_id.planned_revenue
+ self.title_action = self.lead_id.title_action
+
+ @api.onchange('next_activity_id')
+ def onchange_next_activity_id(self):
+ if not self.title_action:
+ self.title_action = self.next_activity_id.description
+ date_action = False
+ if self.next_activity_id and self.next_activity_id.days:
+ date_action = (datetime.now() + timedelta(days=self.next_activity_id.days)).strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)
+ self.date_action = date_action
+
+ @api.onchange('recommended_activity_id')
+ def onchange_recommended_activity_id(self):
+ self.next_activity_id = self.recommended_activity_id
+
+ @api.multi
+ def action_log_and_schedule(self):
+ self.ensure_one()
+ self.action_log()
+ view_id = self.env.ref('crm.crm_activity_log_view_form_schedule')
+ return {
+ 'name': _('Next activity'),
+ 'res_model': 'crm.activity.log',
+ 'context': {
+ 'default_last_activity_id': self.next_activity_id.id,
+ 'default_lead_id': self.lead_id.id
+ },
+ 'type': 'ir.actions.act_window',
+ 'view_id': False,
+ 'views': [(view_id.id, 'form')],
+ 'view_mode': 'form',
+ 'target': 'new',
+ 'view_type': 'form',
+ 'res_id': False
+ }
+
+ @api.multi
+ def action_log(self):
+ for log in self:
+ body_html = "
%(title)s: %(next_activity)s
%(description)s%(note)s" % {
+ 'title': _('Activity Done'),
+ 'next_activity': log.next_activity_id.name,
+ 'description': log.title_action and '
%s
' % log.title_action or '',
+ 'note': log.note or '',
+ }
+ log.lead_id.message_post(body_html, subtype_id=log.next_activity_id.subtype_id.id)
+ log.lead_id.write({
+ 'date_deadline': log.date_deadline,
+ 'planned_revenue': log.planned_revenue,
+ 'title_action': False,
+ 'date_action': False,
+ 'next_activity_id': False,
+ })
+ return True
+
+ @api.multi
+ def action_schedule(self):
+ for log in self:
+ log.lead_id.write({
+ 'title_action': log.title_action,
+ 'date_action': log.date_action,
+ 'next_activity_id': log.next_activity_id.id,
+ })
+ return True
diff --git a/addons/crm/wizard/crm_activity_log_views.xml b/addons/crm/wizard/crm_activity_log_views.xml
new file mode 100644
index 00000000000..73988958124
--- /dev/null
+++ b/addons/crm/wizard/crm_activity_log_views.xml
@@ -0,0 +1,74 @@
+
+
+
+ Log an Activity
+ crm.activity.log
+ 20
+
+
+
+
+
+
+ Schedule next Activity
+ crm.activity.log
+ 40
+
+
+
+
+
+
+ Log an Activity
+ ir.actions.act_window
+ crm.activity.log
+ form
+
+ new
+
+
\ No newline at end of file