From fac5f1ee3f63d3ceb66e834478237b3a1392f052 Mon Sep 17 00:00:00 2001 From: cubells Date: Tue, 2 May 2017 18:11:53 +0200 Subject: [PATCH] [MIG][9.0] crm_action module --- crm_action/README.rst | 22 ++- crm_action/__openerp__.py | 17 +- crm_action/data/email_reminder.xml | 60 +++--- crm_action/demo/demo.xml | 128 ++++++------ .../migrations/8.0.1.2.0/pre-migration.py | 14 -- crm_action/models/crm_action.py | 104 +++++----- crm_action/models/crm_action_type.py | 20 +- crm_action/models/crm_lead.py | 52 +++-- crm_action/security/ir_rule_data.xml | 40 ++-- crm_action/tests/__init__.py | 5 + crm_action/tests/test_crm_action.py | 57 ++++++ crm_action/views/crm_action_type_view.xml | 91 +++++---- crm_action/views/crm_action_view.xml | 185 +++++++++--------- crm_action/views/crm_lead_view.xml | 91 +++++---- 14 files changed, 487 insertions(+), 399 deletions(-) delete mode 100644 crm_action/migrations/8.0.1.2.0/pre-migration.py create mode 100644 crm_action/tests/__init__.py create mode 100644 crm_action/tests/test_crm_action.py diff --git a/crm_action/README.rst b/crm_action/README.rst index 62b964e0970..7f3372bc8cc 100644 --- a/crm_action/README.rst +++ b/crm_action/README.rst @@ -7,7 +7,8 @@ CRM Action ========== This module was written to extend CRM features. -It delivers new object named "Actions" to follow history around leads and opportunities. +It delivers new object named "Actions" to follow history around leads and +opportunities. Installation ============ @@ -17,9 +18,14 @@ Just install the module as usual (it only depends on the native *crm* module). Configuration ============= -Go to the menu *Sales > Configuration > Leads & Opportunities > Action Types* and create action types. +Go to the menu *Sales > Configuration > Leads & Opportunities > Action Types* +and create action types. -If you want to have a daily email reminder of your CRM actions to do, go to the menu *Settings > Technical > Automation > Scheduled Actions* and activate the action *CRM Action email reminder* (it is inactive by default). You can customize the email template in the menu *Settings > Technical > Email > Templates* and select the email template named *CRM Action reminder*. +If you want to have a daily email reminder of your CRM actions to do, go to +the menu *Settings > Technical > Automation > Scheduled Actions* and activate +the action *CRM Action email reminder* (it is inactive by default). You can +customize the email template in the menu *Settings > Technical > Email > +Templates* and select the email template named *CRM Action reminder*. Usage ===== @@ -30,11 +36,14 @@ To use this module, you need to : #. create a new action by using the *Actions* button, #. when the action is done, click on the button *Mark as done*. -You can overview all actions for any lead or opportunity with the *Actions* menu entry. On the form view of an opportunity, you can see the next action to do and there is also a button to mark it as done (it will immediately display the new next action to do). +You can overview all actions for any lead or opportunity with the *Actions* +menu entry. On the form view of an opportunity, you can see the next action to +do and there is also a button to mark it as done (it will immediately display +the new next action to do). .. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas :alt: Try me on Runbot - :target: https://runbot.odoo-community.org/runbot/111/8.0 + :target: https://runbot.odoo-community.org/runbot/111/9.0 Known issues / Roadmap ====================== @@ -50,8 +59,6 @@ help us smashing it by providing a detailed and welcomed feedback. Credits ======= -Module developed and tested with Odoo version 8.0 - Contributors ------------ @@ -59,6 +66,7 @@ Contributors * Jordi RIERA * Bruno JOLIVEAU * Alexis de Lattre +* Vicent Cubells Maintainer ---------- diff --git a/crm_action/__openerp__.py b/crm_action/__openerp__.py index 922b1d969a7..bcf19991b1c 100644 --- a/crm_action/__openerp__.py +++ b/crm_action/__openerp__.py @@ -1,15 +1,20 @@ # -*- coding: utf-8 -*- -# © 2015-2016 Savoir-faire Linux () +# Copyright 2015-2016 Savoir-faire Linux () +# Copyright 2017 Tecnativa - Vicent Cubells # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). { 'name': 'CRM Action', - 'version': '8.0.1.2.0', - 'author': 'Savoir-faire Linux,Odoo Community Association (OCA)', + 'version': '9.0.1.0.0', + 'author': 'Savoir-faire Linux, ' + 'Tecnativa, ' + 'Odoo Community Association (OCA)', 'license': 'AGPL-3', 'category': 'CRM', 'summary': 'Adds action management in CRM', - 'depends': ['crm'], + 'depends': [ + 'crm', + ], 'data': [ 'security/ir.model.access.csv', 'security/ir_rule_data.xml', @@ -18,7 +23,9 @@ 'views/crm_lead_view.xml', 'data/email_reminder.xml', ], - 'demo': ['demo/demo.xml'], + 'demo': [ + 'demo/demo.xml', + ], 'installable': True, 'application': True, } diff --git a/crm_action/data/email_reminder.xml b/crm_action/data/email_reminder.xml index 29f40b6e3dd..17b1ef533ad 100644 --- a/crm_action/data/email_reminder.xml +++ b/crm_action/data/email_reminder.xml @@ -1,36 +1,34 @@ + + + + CRM Action email reminder + + + 1 + days + -1 + + + + + + + - - - - - CRM Action email reminder - - - 1 - days - -1 - - - - - - - - - - - CRM Action Reminder - - - ${ctx.get('company').email or 'odoo@example.com'} - ${object.email} - [${ctx.get('company').name}] Today's CRM actions - + CRM Action Reminder + + + ${ctx.get('company').email or 'odoo@example.com'} + ${object.email} + [${ctx.get('company').name}] Today's CRM actions +

Dear ${object.name},

@@ -70,7 +68,7 @@ Automatic e-mail sent by Odoo. Do not reply.

]]>
-
+ -
-
+
+
diff --git a/crm_action/demo/demo.xml b/crm_action/demo/demo.xml index c73d1a47790..b787a944b48 100644 --- a/crm_action/demo/demo.xml +++ b/crm_action/demo/demo.xml @@ -1,77 +1,73 @@ - - + + + Meeting + - - Meeting - + + Followup + - - Followup - + + Demo + - - Demo - + + Get news about our quote + + + + + + done + - - Get news about our quote - - - - - - done - + + Meeting + + + + + + draft + - - Meeting - - - - - - draft - + + Organise a demo in our showroom + + + + + + draft + - - Organise a demo in our showroom - - - - - - draft - + + Get feedback about our quote + + + + + done + - - Get feedback about our quote - - - - - done - + + Meet in real life + + + + + draft + - - Meet in real life - - - - - draft - + + Organise a super demo to convince him that it is super easy and super fast + + + + + draft + - - Organise a super demo to convince him that it is super easy and super fast - - - - - draft - - - - - + diff --git a/crm_action/migrations/8.0.1.2.0/pre-migration.py b/crm_action/migrations/8.0.1.2.0/pre-migration.py deleted file mode 100644 index 4923bcada0d..00000000000 --- a/crm_action/migrations/8.0.1.2.0/pre-migration.py +++ /dev/null @@ -1,14 +0,0 @@ -# -*- coding: utf-8 -*- -# © 2016 Akretion (Alexis de Lattre ) -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - - -def migrate(cr, version): - if not version: - return - - cr.execute( - 'ALTER TABLE "crm_action_type" RENAME "is_active" TO "active"') - - cr.execute( - 'ALTER TABLE "crm_action" RENAME "action_type" TO "action_type_id"') diff --git a/crm_action/models/crm_action.py b/crm_action/models/crm_action.py index 9163dc4f8a9..111a2591fa2 100644 --- a/crm_action/models/crm_action.py +++ b/crm_action/models/crm_action.py @@ -1,8 +1,9 @@ # -*- coding: utf-8 -*- -# © 2015-2016 Savoir-faire Linux () +# Copyright 2015-2016 Savoir-faire Linux () +# Copyright 2017 Tecnativa - Vicent Cubells # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from openerp import models, fields, api +from openerp import api, fields, models import logging logger = logging.getLogger(__name__) @@ -14,8 +15,57 @@ class CrmAction(models.Model): _order = 'date' _rec_name = 'display_name' + def default_action_type(self): + action_types = self.search_action_types() + return action_types and action_types[0].id or False + lead_id = fields.Many2one( - 'crm.lead', string='Lead', ondelete='cascade') + comodel_name='crm.lead', + string='Lead', + ondelete='cascade', + ) + company_id = fields.Many2one( + comodel_name='res.company', + string='Company', + default=lambda self: self.env['res.company']._company_default_get( + 'crm.action'), + ) + partner_id = fields.Many2one( + comodel_name='res.partner', + string='Customer', + ) + date = fields.Date( + required=True, + default=fields.Date.context_today, + ) + user_id = fields.Many2one( + comodel_name='res.users', + string='User', + required=True, + default=lambda self: self.env.user, + ) + action_type_id = fields.Many2one( + comodel_name='crm.action.type', + string='Type', + required=True, + default=default_action_type, + ) + details = fields.Text() + state = fields.Selection( + selection=[ + ('draft', 'Todo'), + ('done', 'Done'), + ], + string='Status', + required=True, + readonly=True, + default="draft", + ) + display_name = fields.Char( + compute='compute_display_name', + readonly=True, + store=True, + ) @api.onchange('lead_id') def check_change(self): @@ -24,41 +74,16 @@ def check_change(self): self.partner_id = lead.partner_id self.company_id = lead.company_id - company_id = fields.Many2one( - 'res.company', string='Company', - default=lambda self: self.env['res.company']._company_default_get( - 'crm.action')) - - partner_id = fields.Many2one( - 'res.partner', string='Customer') - - date = fields.Date( - 'Date', required=True, - default=fields.Date.context_today) - - user_id = fields.Many2one( - 'res.users', string='User', required=True, - default=lambda self: self.env.user) - def search_action_types(self): return self.env['crm.action.type'].search([], order='priority') - def default_action_type(self): - action_types = self.search_action_types() - return action_types and action_types[0].id or False - - action_type_id = fields.Many2one( - 'crm.action.type', string='Type', required=True, - default=default_action_type) - - details = fields.Text('Details') + @api.multi + def button_confirm(self): + self.write({'state': 'done'}) - state = fields.Selection( - [ - ('draft', 'Todo'), - ('done', 'Done'), - ], string='Status', required=True, readonly=True, - default="draft") + @api.multi + def button_set_to_draft(self): + self.write({'state': 'draft'}) @api.multi @api.depends('action_type_id.name', 'details') @@ -70,17 +95,6 @@ def compute_display_name(self): else: action.display_name = u'[%s]' % action.action_type_id.name - display_name = fields.Char( - compute='compute_display_name', readonly=True, store=True) - - @api.multi - def button_confirm(self): - self.write({'state': 'done'}) - - @api.multi - def button_set_to_draft(self): - self.write({'state': 'draft'}) - @api.model def _send_email_reminder(self): today = fields.Date.context_today(self) diff --git a/crm_action/models/crm_action_type.py b/crm_action/models/crm_action_type.py index a07ca97aa50..e6058356dd0 100644 --- a/crm_action/models/crm_action_type.py +++ b/crm_action/models/crm_action_type.py @@ -1,16 +1,26 @@ # -*- coding: utf-8 -*- -# © 2015-2016 Savoir-faire Linux () +# Copyright 2015-2016 Savoir-faire Linux () +# Copyright 2017 Tecnativa - Vicent Cubells # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from openerp import models, fields +from openerp import fields, models class CrmActionType(models.Model): _name = 'crm.action.type' _description = 'CRM Action Type' - name = fields.Char('Name', translate=True, required=True) - priority = fields.Integer('Priority', required=True, default=0) - active = fields.Boolean('Active', default=True) + name = fields.Char( + string='Name', + translate=True, + required=True, + ) + priority = fields.Integer( + string='Priority', + required=True, default=0, + ) + active = fields.Boolean( + default=True, + ) _order = 'priority' diff --git a/crm_action/models/crm_lead.py b/crm_action/models/crm_lead.py index d85295408cf..554afb1e224 100644 --- a/crm_action/models/crm_lead.py +++ b/crm_action/models/crm_lead.py @@ -1,16 +1,44 @@ # -*- coding: utf-8 -*- -# © 2015-2016 Savoir-faire Linux () +# Copyright 2015-2016 Savoir-faire Linux () +# Copyright 2017 Tecnativa - Vicent Cubells # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from openerp import models, fields, api +from openerp import api, fields, models class CrmLead(models.Model): _inherit = 'crm.lead' - def count_actions(self): + def compute_count_actions(self): self.actions_count = len(self.action_ids) + actions_count = fields.Integer( + compute='compute_count_actions', + ) + action_ids = fields.One2many( + comodel_name='crm.action', + inverse_name='lead_id', + string='Actions', + ) + # replace native fields "date_action" (Next Action Date) + # and "title_action" (Next Action) by related fields + date_action = fields.Date( + related='next_action_id.date', + readonly=True, + store=True, + ) + title_action = fields.Char( + related='next_action_id.display_name', + readonly=True, + store=True, + ) + next_action_id = fields.Many2one( + comodel_name='crm.action', + string='Next Action', + compute='compute_next_action', + readonly=True, store=True, + ) + @api.multi @api.depends( 'action_ids.date', 'action_ids.display_name', 'action_ids.state') @@ -27,20 +55,6 @@ def compute_next_action(self): @api.multi def next_action_done(self): self.ensure_one() - lead = self[0] - if lead.next_action_id: - lead.next_action_id.button_confirm() + if self.next_action_id: + self.next_action_id.button_confirm() return True - - actions_count = fields.Integer(compute='count_actions') - action_ids = fields.One2many( - 'crm.action', 'lead_id', string='Actions') - # replace native fields "date_action" (Next Action Date) - # and "title_action" (Next Action) by related fields - date_action = fields.Date( - related='next_action_id.date', readonly=True, store=True) - title_action = fields.Char( - related='next_action_id.display_name', store=True, readonly=True) - next_action_id = fields.Many2one( - 'crm.action', string='Next Action', - compute='compute_next_action', readonly=True, store=True) diff --git a/crm_action/security/ir_rule_data.xml b/crm_action/security/ir_rule_data.xml index 6c95ffeb3a6..1b853afd558 100644 --- a/crm_action/security/ir_rule_data.xml +++ b/crm_action/security/ir_rule_data.xml @@ -1,24 +1,22 @@ - - + - - Personal CRM Actions - - ['|', ('lead_id.user_id', '=', user.id), ('user_id', '=', user.id)] - - - - All CRM Actions - - [(1, '=', 1)] - - - - CRM Actions multi-company - - ['|', ('company_id', '=', False), ('company_id', 'child_of', [user.company_id.id])] - + + Personal CRM Actions + + ['|', ('lead_id.user_id', '=', user.id), ('user_id', '=', user.id)] + + + + All CRM Actions + + [(1, '=', 1)] + + + + CRM Actions multi-company + + ['|', ('company_id', '=', False), ('company_id', 'child_of', [user.company_id.id])] + - - + diff --git a/crm_action/tests/__init__.py b/crm_action/tests/__init__.py new file mode 100644 index 00000000000..435d8c6af8d --- /dev/null +++ b/crm_action/tests/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 Tecnativa - Vicent Cubells +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import test_crm_action diff --git a/crm_action/tests/test_crm_action.py b/crm_action/tests/test_crm_action.py new file mode 100644 index 00000000000..b15590d463f --- /dev/null +++ b/crm_action/tests/test_crm_action.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 Vicent Cubells - +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from openerp.tests import common + + +class TestCrmAction(common.SavepointCase): + @classmethod + def setUpClass(cls): + super(TestCrmAction, cls).setUpClass() + cls.partner = cls.env['res.partner'].create({ + 'name': 'Test partner', + }) + cls.pipeline = cls.env['crm.lead'].create({ + 'name': 'Test lead', + 'partner_id': cls.partner.id, + }) + cls.action1 = cls.env['crm.action'].create({ + 'details': 'Test action #1', + }) + cls.action2 = cls.env['crm.action'].create({ + 'lead_id': cls.pipeline.id, + }) + + def test_crm_action(self): + self.action1.lead_id = self.pipeline.id + self.action1.check_change() + self.assertEqual(self.action1.partner_id, self.partner) + + self.action1.button_confirm() + self.assertEqual(self.action1.state, 'done') + + self.action1.button_set_to_draft() + self.assertEqual(self.action1.state, 'draft') + + self.action1.button_confirm() + + self.assertEqual(self.action2.state, 'draft') + self.assertEqual( + self.pipeline.next_action_id, self.action2) + + self.action2.button_confirm() + self.assertEqual(len(self.pipeline.next_action_id), 0) + + self.action2.button_set_to_draft() + self.assertEqual( + self.pipeline.next_action_id, self.action2) + + self.pipeline.next_action_done() + self.assertEqual( + self.action2.state, 'done') + + self.action2.button_set_to_draft() + self.assertTrue(self.action2._send_email_reminder()) + + self.action2.user_id.email = False + self.action2._send_email_reminder() diff --git a/crm_action/views/crm_action_type_view.xml b/crm_action/views/crm_action_type_view.xml index 9112c01f1ae..262ea957ad8 100644 --- a/crm_action/views/crm_action_type_view.xml +++ b/crm_action/views/crm_action_type_view.xml @@ -1,55 +1,52 @@ - - - - - - crm.action.type.form - crm.action.type - -
- - - - - -
-
-
- - - crm.action.type.tree - crm.action.type - - + + + + crm.action.type.form + crm.action.type + +
+ - - - + +
+
+
- - CRM - Actions - crm.action.type - - - - - - + + crm.action.type.tree + crm.action.type + + + + + + + + + + + CRM - Actions + crm.action.type + + + + + + - - Action Types - crm.action.type - tree - + + Action Types + crm.action.type + tree + - + -
-
+ diff --git a/crm_action/views/crm_action_view.xml b/crm_action/views/crm_action_view.xml index d1319cd2937..3fd5509552d 100644 --- a/crm_action/views/crm_action_view.xml +++ b/crm_action/views/crm_action_view.xml @@ -1,100 +1,99 @@ - - - - crm.action.form - crm.action - -
-
-
- - - - - - - - - - - -
-
-
+ - - crm.action.tree - crm.action - - - - - - - - - - - - + + + - - - -
-
+ + + + CRM - Leads Action Button + crm.lead + + +
+ +
+
+
+ +