[IMP] crm: activities reloaded

Activities in opportunities are now handled through a wizard and not through
the form view anymore. The wizard is called via a Log an activity button. The
wizard allows to log an activity and to schedule the next activity. It
replaces the various <next activities> / Done / Cancel link buttons on the
form view that nobody seemed to inderstand.
This commit is contained in:
Fabien Pinckaers
2015-11-26 10:48:25 +01:00
committed by Thibault Delavallée
parent 01c4998c5c
commit 60c010d306
9 changed files with 226 additions and 118 deletions
+1
View File
@@ -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',
+19 -63
View File
@@ -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 = """<div><b>Activity Done</b>: ${object.next_activity_id.name}</div>
%if object.title_action:
<div>${object.title_action}</div>
%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,
}
+17 -36
View File
@@ -279,12 +279,15 @@
<field name="color"/>
<field name="priority"/>
<field name="planned_revenue"/>
<field name="kanban_state"/>
<field name="date_action"/>
<field name="user_email"/>
<field name="user_id"/>
<field name="partner_address_email"/>
<field name="message_needaction_counter"/>
<field name="tag_ids"/>
<field name="partner_id"/>
<field name="title_action"/>
<field name="active"/>
<field name="company_currency"/>
<templates>
@@ -312,22 +315,22 @@
<strong><field name="name"/></strong>
</div>
<div class="text-muted">
<t t-if="record.planned_revenue.raw_value"><field name="planned_revenue" widget="monetary" options="{'currency_field': 'company_currency'}"/></t> <span t-if="record.partner_id.value"> - <t t-esc="record.partner_id.value"/></span>
</div>
<div class="text-muted">
<t t-if="record.date_deadline.raw_value and record.date_deadline.raw_value lt (new Date())" t-set="red">oe_kanban_text_red</t>
<span t-attf-class="#{red || ''}">
<field name="date_action"/>
<t t-if="record.date_action.raw_value"> : </t>
<field name="next_activity_id"/>
</span>
<t t-if="record.planned_revenue.raw_value"><field name="planned_revenue" widget="monetary" options="{'currency_field': 'company_currency'}"/></t> <span t-if="record.partner_id.value">, <t t-esc="record.partner_id.value"/></span>
</div>
<div class="o_kanban_footer">
<field name="priority" widget="priority" groups="base.group_user"/>
<t t-if="record.message_needaction_counter.raw_value">
<span class='oe_kanban_mail_new' title='Important Messages'><i class='fa fa-comments'/><t t-raw="record.message_needaction_counter.raw_value"/></span>
</t>
<img t-att-src="kanban_image('res.users', 'image_small', record.user_id.raw_value)" t-att-title="record.user_id.value" width="24" height="24" class="oe_kanban_avatar pull-right"/>
<div>
<a name="%(crm.crm_activity_log_action)d" type="action">
<span
t-if="record.date_action.raw_value" t-att-title="record.date_action.value + ': '+ record.title_action.value"
t-attf-class="oe_kanban_status oe_kanban_status_#{record.kanban_state.raw_value}"/>
</a>
<img t-att-src="kanban_image('res.users', 'image_small', record.user_id.raw_value)" t-att-title="record.user_id.value" width="24" height="24" class="oe_kanban_avatar pull-right"/>
</div>
</div>
</div>
<div class="oe_clear"></div>
@@ -381,10 +384,6 @@
</record>
<!--
OPPORTUNITY
-->
<!-- Opportunities Form View -->
<record model="ir.ui.view" id="crm_case_form_view_oppor">
<field name="name">Opportunities</field>
@@ -400,6 +399,8 @@
type="action" class="oe_highlight"
context="{'default_lead_id': active_id}"
attrs="{'invisible': [('active', '=', False)]}"/>
<button name="%(crm.crm_activity_log_action)d" string="Log Activity" type="action" class="btn-link"
attrs="{'invisible': [('active', '=', False)]}"/>
<field name="stage_id" widget="statusbar" clickable="True"
options="{'fold_field': 'fold'}"
domain="['&amp;', ('team_ids', '=', team_id), '|', ('type', '=', type), ('type', '=', 'both')]"
@@ -426,8 +427,6 @@
<div class="oe_title">
<label for="name" class="oe_edit_only"/>
<h1><field name="name" placeholder="e.g. Product Pricing"/></h1>
<h2 class="o_row">
<div>
<label for="planned_revenue" class="oe_edit_only" />
@@ -457,7 +456,6 @@
</group>
<group>
<field name="last_activity_id" invisible="1"/>
<label for="next_activity_id"/>
<div>
<div class="o_row">
@@ -468,27 +466,10 @@
<span class="oe_form_field"
attrs="{'invisible': ['|', ('next_activity_id', '=', False), ('date_action', '=', False)]}">&#160;on&#160;</span>
<field name="date_action" attrs="{'invisible': [('next_activity_id', '=', False)]}" placeholder="Choose a date..."/>
<div attrs="{'invisible': ['|', ('last_activity_id', '=', False), ('next_activity_id', '!=', False)]}">
<button name="log_next_activity_1" type="object" class="btn-link" attrs="{'invisible': [('next_activity_1','=',False)]}">
<field name="next_activity_1" readonly="1"/>
</button>
<span attrs="{'invisible': [('next_activity_2','=',False)]}">|</span>
<button name="log_next_activity_2" type="object" class="btn-link" attrs="{'invisible': [('next_activity_2','=',False)]}">
<field name="next_activity_2" readonly="1"/>
</button>
<span attrs="{'invisible': [('next_activity_3','=',False)]}">|</span>
<button name="log_next_activity_3" type="object" class="btn-link" attrs="{'invisible': [('next_activity_3','=',False)]}">
<field name="next_activity_3" readonly="1"/>
</button>
</div>
</div>
<div class="o_row">
<div>
<field name="title_action" placeholder="e.g. Discuss proposal"
attrs="{'invisible': [('next_activity_id', '=', False)]}"/>
<div attrs="{'invisible': [('next_activity_id', '=', False)]}">
<button name="log_next_activity_done" string="Done" type="object" class="btn-link"/>
<button name="cancel_next_activity" string="Cancel" type="object" class="btn-link"/>
</div>
attrs="{'invisible': [('next_activity_id', '=', False)]}"/>
</div>
</div>
<field name="date_deadline"/>
+5 -9
View File
@@ -7,32 +7,28 @@
<field name="description">Follow-up quote</field>
<field name="days">30</field>
</record>
<record id="crm_activity_demo_make_quote" model="crm.activity">
<field name="name">Make Quote</field>
<field name="description">Send a quotation</field>
<field name="days">15</field>
<field name="activity_1_id" ref="crm_activity_demo_followup_quote"/>
<field name="recommended_activity_ids" eval="[(6,0, [ref('crm.crm_activity_demo_followup_quote')])]"/>
</record>
<record id="crm_activity_demo_call_demo" model="crm.activity">
<field name="name">Call for Demo</field>
<field name="days">10</field>
<field name="description">Call for a demo</field>
<field name="activity_1_id" ref="crm_activity_demo_make_quote"/>
<field name="recommended_activity_ids" eval="[(6, 0, [ref('crm.crm_activity_demo_make_quote')])]"/>
</record>
<record id="crm_activity_data_call" model="crm.activity">
<field name="activity_2_id" ref="crm_activity_demo_call_demo"/>
<field name="days">5</field>
<field name="recommended_activity_ids" eval="[(6, 0, [ref('crm.crm_activity_data_email')])]"/>
</record>
<record id="crm_activity_data_call" model="crm.activity">
<field name="activity_1_id" ref="crm_activity_data_email"/>
<field name="description">Follow-up call</field>
</record>
<record id="crm_activity_data_meeting" model="crm.activity">
<field name="activity_1_id" ref="crm_activity_data_call"/>
<field name="activity_2_id" ref="crm_activity_data_email"/>
<field name="recommended_activity_ids" eval="[(6, 0, [ref('crm_activity_data_call'), ref('crm_activity_data_email')])]"/>
<field name="days">15</field>
</record>
+10 -7
View File
@@ -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):
+1 -3
View File
@@ -27,9 +27,7 @@
<field name="days"/>
</group>
<group>
<field name="activity_1_id"/>
<field name="activity_2_id"/>
<field name="activity_3_id"/>
<field name="recommended_activity_ids" widget="many2many_tags"/>
</group>
</group>
</form>
+1
View File
@@ -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
+98
View File
@@ -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 = "<div><b>%(title)s</b>: %(next_activity)s</div>%(description)s%(note)s" % {
'title': _('Activity Done'),
'next_activity': log.next_activity_id.name,
'description': log.title_action and '<p><em>%s</em></p>' % 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
@@ -0,0 +1,74 @@
<?xml version="1.0"?>
<odoo>
<record id="crm_activity_log_view_form" model="ir.ui.view">
<field name="name">Log an Activity</field>
<field name="model">crm.activity.log</field>
<field name="priority">20</field>
<field name="arch" type="xml">
<form string="Log an Activity">
<group>
<field name="team_id" invisible="1"/>
<field name="lead_id" invisible="1"/>
<group>
<field name="next_activity_id" required="1"
domain="['|', ('team_id', '=', False), ('team_id', '=', team_id)]"
options="{'no_create': True, 'no_open': True}"/>
<field name="title_action" placeholder="e.g. Discuss proposal"/>
</group>
<group>
<field name="planned_revenue"/>
<field name="date_deadline"/>
</group>
</group>
<field name="note" placeholder="Log a note..."/>
<footer>
<button name="action_log_and_schedule" string="Log &amp; Schedule Next" type="object" class="btn btn-primary"/>
<button name="action_log" string="Log Only" type="object" class="btn btn-primary"/>
<button string="Cancel" class="btn btn-default" special="cancel"/>
</footer>
</form>
</field>
</record>
<record id="crm_activity_log_view_form_schedule" model="ir.ui.view">
<field name="name">Schedule next Activity</field>
<field name="model">crm.activity.log</field>
<field name="priority">40</field>
<field name="arch" type="xml">
<form string="Schedule Next Activity">
<group class="mt0">
<field name="team_id" invisible="1"/>
<field name="lead_id" invisible="1"/>
<field name="last_activity_id" invisible="1"/>
<group>
<field name="next_activity_id"
domain="['|', ('team_id', '=', False), ('team_id', '=', team_id)]"
options="{'no_create': True, 'no_open': True}"/>
<field name="date_action"/>
<field name="title_action" placeholder="e.g. Discuss proposal"/>
</group>
<group>
<div>
<p><strong>Recommended activities:</strong></p>
<field name="recommended_activity_id" widget="radio"
domain="[('preceding_activity_ids', 'in', [last_activity_id])]"/>
</div>
</group>
</group>
<footer>
<button name="action_schedule" string="Schedule Activity" type="object" class="btn btn-primary"/>
<button string="No activity" class="btn btn-default" special="cancel"/>
</footer>
</form>
</field>
</record>
<record id="crm_activity_log_action" model="ir.actions.act_window">
<field name="name">Log an Activity</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">crm.activity.log</field>
<field name="view_mode">form</field>
<field name="view_id" ref="crm_activity_log_view_form"/>
<field name="target">new</field>
</record>
</odoo>