From a895cfcded2c50aa4992a8cb98ee6e9c2780d64c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Hennecart?= Date: Mon, 6 Jul 2020 15:42:35 +0000 Subject: [PATCH] [IMP] crm: harmonize won triggers with rainbowman PURPOSE Make sure the Rainbowman animation is rightly triggered whichever way the user marks the opportunity as won. Especially relevant for the onboarding as we want to show the first rainbowman to users completing the tour. SPECIFICATIONS Now the rainbowman and its finely-tuned message are displayed to the user when he uses the statusbar, mark lead as won using action buttons or drag and drop a lead in the won stage in kanban view. LINKS Task ID-2287758 PR #54553 --- addons/crm/controllers/main.py | 3 +- addons/crm/models/crm_lead.py | 33 +- addons/crm/static/src/js/crm_form.js | 53 +++ addons/crm/static/src/js/crm_kanban.js | 52 +++ .../crm/static/tests/crm_rainbowman_tests.js | 360 ++++++++++++++++++ addons/crm/static/tests/mock_server.js | 56 +++ .../crm/static/tests/tours/crm_rainbowman.js | 66 ++++ addons/crm/views/assets.xml | 13 + addons/crm/views/crm_lead_views.xml | 4 +- .../src/js/leads_tree_generate_leads.js | 2 +- 10 files changed, 626 insertions(+), 16 deletions(-) create mode 100644 addons/crm/static/src/js/crm_form.js create mode 100644 addons/crm/static/src/js/crm_kanban.js create mode 100644 addons/crm/static/tests/crm_rainbowman_tests.js create mode 100644 addons/crm/static/tests/mock_server.js create mode 100644 addons/crm/static/tests/tours/crm_rainbowman.js diff --git a/addons/crm/controllers/main.py b/addons/crm/controllers/main.py index 4f1a6960645..f9d12a6c236 100644 --- a/addons/crm/controllers/main.py +++ b/addons/crm/controllers/main.py @@ -4,6 +4,7 @@ import logging from odoo.addons.mail.controllers.main import MailController from odoo import http +from odoo.http import request _logger = logging.getLogger(__name__) @@ -15,7 +16,7 @@ class CrmController(http.Controller): comparison, record, redirect = MailController._check_token_and_record_or_redirect('crm.lead', int(res_id), token) if comparison and record: try: - record.action_set_won() + record.action_set_won_rainbowman() except Exception: _logger.exception("Could not mark crm.lead as won") return MailController._redirect_to_messaging() diff --git a/addons/crm/models/crm_lead.py b/addons/crm/models/crm_lead.py index 48008cf577c..4827eddd040 100644 --- a/addons/crm/models/crm_lead.py +++ b/addons/crm/models/crm_lead.py @@ -739,6 +739,26 @@ class Lead(models.Model): self.ensure_one() self.action_set_won() + message = self._get_rainbowman_message() + if message: + return { + 'effect': { + 'fadeout': 'slow', + 'message': message, + 'img_url': '/web/image/%s/%s/image_1024' % (self.team_id.user_id._name, self.team_id.user_id.id) if self.team_id.user_id.image_1024 else '/web/static/src/img/smile.svg', + 'type': 'rainbow_man', + } + } + return True + + def get_rainbowman_message(self): + self.ensure_one() + if self.stage_id.is_won: + return self._get_rainbowman_message() + return False + + def _get_rainbowman_message(self): + message = False if self.user_id and self.team_id and self.expected_revenue: query = """ SELECT @@ -763,7 +783,6 @@ class Lead(models.Model): 'team_id': self.team_id.id}) query_result = self.env.cr.dictfetchone() - message = False if query_result['total_won'] == 1: message = _('Go, go, go! Congrats for your first deal.') elif query_result['max_team_30'] == self.expected_revenue: @@ -774,17 +793,7 @@ class Lead(models.Model): message = _('You just beat your personal record for the past 30 days.') elif query_result['max_user_7'] == self.expected_revenue: message = _('You just beat your personal record for the past 7 days.') - - if message: - return { - 'effect': { - 'fadeout': 'slow', - 'message': message, - 'img_url': '/web/image/%s/%s/image_1024' % (self.team_id.user_id._name, self.team_id.user_id.id) if self.team_id.user_id.image_1024 else '/web/static/src/img/smile.svg', - 'type': 'rainbow_man', - } - } - return True + return message def action_schedule_meeting(self): """ Open meeting's calendar view to schedule meeting on current opportunity. diff --git a/addons/crm/static/src/js/crm_form.js b/addons/crm/static/src/js/crm_form.js new file mode 100644 index 00000000000..b8f026e618f --- /dev/null +++ b/addons/crm/static/src/js/crm_form.js @@ -0,0 +1,53 @@ +odoo.define("crm.crm_form", function (require) { + "use strict"; + + /** + * This From Controller makes sure we display a rainbowman message + * when the stage is won, even when we click on the statusbar. + * When the stage of a lead is changed and data are saved, we check + * if the lead is won and if a message should be displayed to the user + * with a rainbowman like when the user click on the button "Mark Won". + */ + + var FormController = require('web.FormController'); + var FormView = require('web.FormView'); + var viewRegistry = require('web.view_registry'); + + var CrmFormController = FormController.extend({ + /** + * After data are saved we display a possible rainbowman + * message when the stage is won. + * @override + */ + _applyChanges: async function (dataPointID, changes, event) { + var result = await this._super(...arguments); + if ('stage_id' in changes) { + const message = await this._rpc({ + model: 'crm.lead', + method : 'get_rainbowman_message', + args: [[parseInt(event.target.res_id)]], + }); + if (message) { + this.trigger_up('show_effect', { + message: message, + type: 'rainbow_man', + }); + } + } + return result; + }, + }); + + var CrmFormView = FormView.extend({ + config: _.extend({}, FormView.prototype.config, { + Controller: CrmFormController, + }), + }); + + viewRegistry.add('crm_form', CrmFormView); + + return { + CrmFormController: CrmFormController, + CrmFormView: CrmFormView, + }; +}); diff --git a/addons/crm/static/src/js/crm_kanban.js b/addons/crm/static/src/js/crm_kanban.js new file mode 100644 index 00000000000..dbc50064c7a --- /dev/null +++ b/addons/crm/static/src/js/crm_kanban.js @@ -0,0 +1,52 @@ +odoo.define('crm.crm_kanban', function (require) { + "use strict"; + + /** + * This Kanban Model make sure we display a rainbowman + * message when a lead is won after we moved it in the + * correct column and when it's grouped by stage_id (default). + */ + + var KanbanModel = require('web.KanbanModel'); + var KanbanView = require('web.KanbanView'); + var viewRegistry = require('web.view_registry'); + + var CrmKanbanModel = KanbanModel.extend({ + /** + * Check if the kanban view is grouped by "stage_id" before checking if the lead is won + * and displaying a possible rainbowman message. + * @override + */ + moveRecord: async function (recordID, groupID, parentID) { + var result = await this._super(...arguments); + if (this.localData[parentID].groupedBy[0] === this.defaultGroupedBy[0]) { + const message = await this._rpc({ + model: 'crm.lead', + method : 'get_rainbowman_message', + args: [[parseInt(this.localData[recordID].res_id)]], + }); + if (message) { + this.trigger_up('show_effect', { + message: message, + type: 'rainbow_man', + }); + } + } + return result; + }, + }); + + var CrmKanbanView = KanbanView.extend({ + config: _.extend({}, KanbanView.prototype.config, { + Model: CrmKanbanModel, + }), + }); + + viewRegistry.add('crm_kanban', CrmKanbanView); + + return { + CrmKanbanModel: CrmKanbanModel, + CrmKanbanView: CrmKanbanView, + }; + +}); diff --git a/addons/crm/static/tests/crm_rainbowman_tests.js b/addons/crm/static/tests/crm_rainbowman_tests.js new file mode 100644 index 00000000000..ff7f91967eb --- /dev/null +++ b/addons/crm/static/tests/crm_rainbowman_tests.js @@ -0,0 +1,360 @@ +odoo.define('crm.form_rainbowman_tests', function (require) { + "use strict"; + + var CrmFormView = require('crm.crm_form').CrmFormView; + var CrmKanbanView = require('crm.crm_kanban').CrmKanbanView; + var testUtils = require('web.test_utils'); + var createView = testUtils.createView; + + QUnit.module('Crm Rainbowman Triggers', { + beforeEach: function () { + const format = "YYYY-MM-DD HH:mm:ss"; + this.data = { + 'res.users': { + fields: { + display_name: { string: 'Name', type: 'char' }, + }, + records: [ + { id: 1, name: 'Mario' }, + { id: 2, name: 'Luigi' }, + { id: 3, name: 'Link' }, + { id: 4, name: 'Zelda' }, + ], + }, + 'crm.team': { + fields: { + display_name: { string: 'Name', type: 'char' }, + member_ids: { string: 'Members', type: 'many2many', relation: 'res.users' }, + }, + records: [ + { id: 1, name: 'Mushroom Kingdom', member_ids: [1, 2] }, + { id: 2, name: 'Hyrule', member_ids: [3, 4] }, + ], + }, + 'crm.stage': { + fields: { + display_name: { string: 'Name', type: 'char' }, + is_won: { string: 'Is won', type: 'boolean' }, + }, + records: [ + { id: 1, name: 'Start' }, + { id: 2, name: 'Middle' }, + { id: 3, name: 'Won', is_won: true}, + ], + }, + 'crm.lead': { + fields: { + display_name: { string: 'Name', type: 'char' }, + planned_revenue: { string: 'Revenue', type: 'float' }, + stage_id: { string: 'Stage', type: 'many2one', relation: 'crm.stage' }, + team_id: { string: 'Sales Team', type: 'many2one', relation: 'crm.team' }, + user_id: { string: 'Salesperson', type: 'many2one', relation: 'res.users' }, + date_closed: { string: 'Date closed', type: 'datetime' }, + }, + records : [ + { id: 1, name: 'Lead 1', planned_revenue: 5.0, stage_id: 1, team_id: 1, user_id: 1 }, + { id: 2, name: 'Lead 2', planned_revenue: 5.0, stage_id: 2, team_id: 2, user_id: 4 }, + { id: 3, name: 'Lead 3', planned_revenue: 3.0, stage_id: 3, team_id: 1, user_id: 1, date_closed: moment().subtract(5, 'days').format(format) }, + { id: 4, name: 'Lead 4', planned_revenue: 4.0, stage_id: 3, team_id: 2, user_id: 4, date_closed: moment().subtract(23, 'days').format(format) }, + { id: 5, name: 'Lead 5', planned_revenue: 7.0, stage_id: 3, team_id: 1, user_id: 1, date_closed: moment().subtract(20, 'days').format(format) }, + { id: 6, name: 'Lead 6', planned_revenue: 4.0, stage_id: 2, team_id: 1, user_id: 2 }, + { id: 7, name: 'Lead 7', planned_revenue: 1.8, stage_id: 3, team_id: 2, user_id: 3, date_closed: moment().subtract(23, 'days').format(format) }, + { id: 8, name: 'Lead 8', planned_revenue: 1.9, stage_id: 1, team_id: 2, user_id: 3 }, + { id: 9, name: 'Lead 9', planned_revenue: 1.5, stage_id: 3, team_id: 2, user_id: 3, date_closed: moment().subtract(5, 'days').format(format) }, + { id: 10, name: 'Lead 10', planned_revenue: 1.7, stage_id: 2, team_id: 2, user_id: 3 }, + { id: 11, name: 'Lead 11', planned_revenue: 2.0, stage_id: 3, team_id: 2, user_id: 4, date_closed: moment().subtract(5, 'days').format(format) }, + ], + }, + }; + this.testFormView = { + arch: ` +
+
+ + + + + `, + data: this.data, + model: 'crm.lead', + View: CrmFormView, + }; + this.testKanbanView = { + arch: ` + + + +
+
+
+
`, + data: this.data, + model: 'crm.lead', + View: CrmKanbanView, + groupBy: ['stage_id'], + }; + }, + }, function () { + QUnit.test("first lead won, click on statusbar", async function (assert) { + assert.expect(2); + + this.testFormView.res_id = 6; + this.testFormView.mockRPC = async function (route, args) { + const result = await this._super(...arguments); + if (args.model === 'crm.lead' && args.method === 'get_rainbowman_message') { + assert.step(result || "no rainbowman"); + } + return result; + }; + const form = await createView(this.testFormView); + + await testUtils.dom.click(form.$(".o_statusbar_status button[data-value='3']")); + assert.verifySteps(['Go, go, go! Congrats for your first deal.']); + + form.destroy(); + }); + + QUnit.test("team record 30 days, click on statusbar", async function (assert) { + assert.expect(2); + + this.testFormView.res_id = 2; + this.testFormView.mockRPC = async function (route, args) { + const result = await this._super(...arguments); + if (args.model === 'crm.lead' && args.method === 'get_rainbowman_message') { + assert.step(result || "no rainbowman"); + } + return result; + }; + const form = await createView(this.testFormView); + + await testUtils.dom.click(form.$(".o_statusbar_status button[data-value='3']")); + assert.verifySteps(['Boom! Team record for the past 30 days.']); + + form.destroy(); + }); + + QUnit.test("team record 7 days, click on statusbar", async function (assert) { + assert.expect(2); + + this.testFormView.res_id = 1; + this.testFormView.mockRPC = async function (route, args) { + const result = await this._super(...arguments); + if (args.model === 'crm.lead' && args.method === 'get_rainbowman_message') { + assert.step(result || "no rainbowman"); + } + return result; + }; + const form = await createView(this.testFormView); + + await testUtils.dom.click(form.$(".o_statusbar_status button[data-value='3']")); + assert.verifySteps(['Yeah! Deal of the last 7 days for the team.']); + + form.destroy(); + }); + + QUnit.test("user record 30 days, click on statusbar", async function (assert) { + assert.expect(2); + + this.testFormView.res_id = 8; + this.testFormView.mockRPC = async function (route, args) { + const result = await this._super(...arguments); + if (args.model === 'crm.lead' && args.method === 'get_rainbowman_message') { + assert.step(result || "no rainbowman"); + } + return result; + }; + const form = await createView(this.testFormView); + + await testUtils.dom.click(form.$(".o_statusbar_status button[data-value='3']")); + assert.verifySteps(['You just beat your personal record for the past 30 days.']); + + form.destroy(); + }); + + QUnit.test("user record 7 days, click on statusbar", async function (assert) { + assert.expect(2); + + this.testFormView.res_id = 10; + this.testFormView.mockRPC = async function (route, args) { + const result = await this._super(...arguments); + if (args.model === 'crm.lead' && args.method === 'get_rainbowman_message') { + assert.step(result || "no rainbowman"); + } + return result; + }; + const form = await createView(this.testFormView); + + await testUtils.dom.click(form.$(".o_statusbar_status button[data-value='3']")); + assert.verifySteps(['You just beat your personal record for the past 7 days.']); + + form.destroy(); + }); + + QUnit.test("click on stage (not won) on statusbar", async function (assert) { + assert.expect(2); + + this.testFormView.res_id = 1; + this.testFormView.mockRPC = async function (route, args) { + const result = await this._super(...arguments); + if (args.model === 'crm.lead' && args.method === 'get_rainbowman_message') { + assert.step(result || "no rainbowman"); + } + return result; + }; + const form = await createView(this.testFormView); + + await testUtils.dom.click(form.$(".o_statusbar_status button[data-value='2']")); + assert.verifySteps(['no rainbowman']); + + form.destroy(); + }); + + QUnit.test("first lead won, drag & drop kanban", async function (assert) { + assert.expect(2); + + this.testKanbanView.mockRPC = async function (route, args) { + const result = await this._super(...arguments); + if (args.model === 'crm.lead' && args.method === 'get_rainbowman_message') { + assert.step(result || "no rainbowman"); + } + return result; + }; + const kanban = await createView(this.testKanbanView); + + kanban.model.defaultGroupedBy = ['stage_id']; + await kanban.reload(); + + await testUtils.dom.dragAndDrop(kanban.$('.o_kanban_record:contains("Lead 6")'), kanban.$('.o_kanban_group:eq(2)')); + assert.verifySteps(['Go, go, go! Congrats for your first deal.']); + + kanban.destroy(); + }); + + QUnit.test("team record 30 days, drag & drop kanban", async function (assert) { + assert.expect(2); + + this.testKanbanView.mockRPC = async function (route, args) { + const result = await this._super(...arguments); + if (args.model === 'crm.lead' && args.method === 'get_rainbowman_message') { + assert.step(result || "no rainbowman"); + } + return result; + }; + const kanban = await createView(this.testKanbanView); + + kanban.model.defaultGroupedBy = ['stage_id']; + await kanban.reload(); + + await testUtils.dom.dragAndDrop(kanban.$('.o_kanban_record:contains("Lead 2")'), kanban.$('.o_kanban_group:eq(2)')); + assert.verifySteps(['Boom! Team record for the past 30 days.']); + + kanban.destroy(); + }); + + QUnit.test("team record 7 days, drag & drop kanban", async function (assert) { + assert.expect(2); + + this.testKanbanView.mockRPC = async function (route, args) { + const result = await this._super(...arguments); + if (args.model === 'crm.lead' && args.method === 'get_rainbowman_message') { + assert.step(result || "no rainbowman"); + } + return result; + }; + const kanban = await createView(this.testKanbanView); + + kanban.model.defaultGroupedBy = ['stage_id']; + await kanban.reload(); + + await testUtils.dom.dragAndDrop(kanban.$('.o_kanban_group:eq(0) .o_kanban_record:contains("Lead 1")'), kanban.$('.o_kanban_group:eq(2)')); + assert.verifySteps(['Yeah! Deal of the last 7 days for the team.']); + + kanban.destroy(); + }); + + QUnit.test("user record 30 days, drag & drop kanban", async function (assert) { + assert.expect(2); + + this.testKanbanView.mockRPC = async function (route, args) { + const result = await this._super(...arguments); + if (args.model === 'crm.lead' && args.method === 'get_rainbowman_message') { + assert.step(result || "no rainbowman"); + } + return result; + }; + const kanban = await createView(this.testKanbanView); + + kanban.model.defaultGroupedBy = ['stage_id']; + await kanban.reload(); + + await testUtils.dom.dragAndDrop(kanban.$('.o_kanban_record:contains("Lead 8")'), kanban.$('.o_kanban_group:eq(2)')); + assert.verifySteps(['You just beat your personal record for the past 30 days.']); + + kanban.destroy(); + }); + + QUnit.test("user record 7 days, drag & drop kanban", async function (assert) { + assert.expect(2); + + this.testKanbanView.mockRPC = async function (route, args) { + const result = await this._super(...arguments); + if (args.model === 'crm.lead' && args.method === 'get_rainbowman_message') { + assert.step(result || "no rainbowman"); + } + return result; + }; + const kanban = await createView(this.testKanbanView); + + kanban.model.defaultGroupedBy = ['stage_id']; + await kanban.reload(); + + await testUtils.dom.dragAndDrop(kanban.$('.o_kanban_record:contains("Lead 10")'), kanban.$('.o_kanban_group:eq(2)')); + assert.verifySteps(['You just beat your personal record for the past 7 days.']); + + kanban.destroy(); + }); + + QUnit.test("drag & drop record kanban in stage not won", async function (assert) { + assert.expect(2); + + this.testKanbanView.mockRPC = async function (route, args) { + const result = await this._super(...arguments); + if (args.model === 'crm.lead' && args.method === 'get_rainbowman_message') { + assert.step(result || "no rainbowman"); + } + return result; + }; + const kanban = await createView(this.testKanbanView); + + kanban.model.defaultGroupedBy = ['stage_id']; + await kanban.reload(); + + await testUtils.dom.dragAndDrop(kanban.$('.o_kanban_record:contains("Lead 8")'), kanban.$('.o_kanban_group:eq(1)')); + assert.verifySteps(["no rainbowman"]); + + kanban.destroy(); + }); + + QUnit.test("drag & drop record in kanban not grouped by stage_id", async function (assert) { + assert.expect(1); + + this.testKanbanView.mockRPC = async function (route, args) { + const result = await this._super(...arguments); + if (args.model === 'crm.lead' && args.method === 'get_rainbowman_message') { + assert.step(result || "no rainbowman"); + } + return result; + }; + this.testKanbanView.groupBy = ['user_id']; + const kanban = await createView(this.testKanbanView); + + kanban.model.defaultGroupedBy = ['stage_id']; + await kanban.reload(); + + await testUtils.dom.dragAndDrop(kanban.$('.o_kanban_group:eq(0) .o_kanban_record:first'), kanban.$('.o_kanban_group:eq(1)')); + assert.verifySteps([]); // Should never pass by the rpc + + kanban.destroy(); + }); + }); +}); diff --git a/addons/crm/static/tests/mock_server.js b/addons/crm/static/tests/mock_server.js new file mode 100644 index 00000000000..0c6c0fbb64e --- /dev/null +++ b/addons/crm/static/tests/mock_server.js @@ -0,0 +1,56 @@ +odoo.define('crm.MockServer', function (require) { + 'use strict'; + + var MockServer = require('web.MockServer'); + + MockServer.include({ + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * @override + * @private + */ + async _performRpc(route, args) { + if (args.model === 'crm.lead' && args.method === 'get_rainbowman_message') { + let message = false; + const records = this.data['crm.lead'].records; + const record = records.find(r => r.id === args.args[0][0]); + const won_stage = this.data['crm.stage'].records.find(s => s.is_won); + if (record.stage_id === won_stage.id && record.user_id && record.team_id && record.planned_revenue > 0) { + const now = moment(); + let query_result = {}; + // Total won + query_result['total_won'] = records.filter(r => r.stage_id === won_stage.id && r.user_id === record.user_id).length; + // Max team 30 days + const recordsTeam30 = records.filter(r => r.stage_id === won_stage.id && r.team_id === record.team_id && (!r.date_closed || moment.duration(now.diff(moment(r.date_closed))).days() <= 30)); + query_result['max_team_30'] = Math.max(...recordsTeam30.map(r => r.planned_revenue)); + // Max team 7 days + const recordsTeam7 = records.filter(r => r.stage_id === won_stage.id && r.team_id === record.team_id && (!r.date_closed || moment.duration(now.diff(moment(r.date_closed))).days() <= 7)); + query_result['max_team_7'] = Math.max(...recordsTeam7.map(r => r.planned_revenue)); + // Max User 30 days + const recordsUser30 = records.filter(r => r.stage_id === won_stage.id && r.user_id === record.user_id && (!r.date_closed || moment.duration(now.diff(moment(r.date_closed))).days() <= 30)); + query_result['max_user_30'] = Math.max(...recordsUser30.map(r => r.planned_revenue)); + // Max User 7 days + const recordsUser7 = records.filter(r => r.stage_id === won_stage.id && r.user_id === record.user_id && (!r.date_closed || moment.duration(now.diff(moment(r.date_closed))).days() <= 7)); + query_result['max_user_7'] = Math.max(...recordsUser7.map(r => r.planned_revenue)); + + if (query_result.total_won === 1) { + message = "Go, go, go! Congrats for your first deal."; + } else if (query_result.max_team_30 === record.planned_revenue) { + message = "Boom! Team record for the past 30 days."; + } else if (query_result.max_team_7 === record.planned_revenue) { + message = "Yeah! Deal of the last 7 days for the team."; + } else if (query_result.max_user_30 === record.planned_revenue) { + message = "You just beat your personal record for the past 30 days."; + } else if (query_result.max_user_7 === record.planned_revenue) { + message = "You just beat your personal record for the past 7 days."; + } + } + return message; + } + return this._super(...arguments); + }, + }); +}); diff --git a/addons/crm/static/tests/tours/crm_rainbowman.js b/addons/crm/static/tests/tours/crm_rainbowman.js new file mode 100644 index 00000000000..67c488b816e --- /dev/null +++ b/addons/crm/static/tests/tours/crm_rainbowman.js @@ -0,0 +1,66 @@ +odoo.define('crm.tour_crm_rainbowman', function (require) { + "use strict"; + + var tour = require('web_tour.tour'); + + tour.register('crm_rainbowman', { + test: true, + url: "/web", + }, [ + tour.stepUtils.showAppsMenuItem(), + { + trigger: ".o_app[data-menu-xmlid='crm.crm_menu_root']", + content: "open crm app", + }, { + trigger: ".o-kanban-button-new", + content: "click create", + }, { + trigger: "input[name=name]", + content: "complete name", + run: "text Test Lead 1", + }, { + trigger: "div[name=planned_revenue] > input", + content: "complete planned revenue", + run: "text 999999997", + }, { + trigger: "button.o_kanban_add", + content: "create lead", + }, { + trigger: ".o_kanban_record .o_kanban_record_title:contains('Test Lead 1')", + content: "move to won stage", + run: "drag_and_drop .o_opportunity_kanban .o_kanban_group:eq(3) " + }, { + trigger: ".o-kanban-button-new", + extra_trigger: ".o_reward_rainbow", + content: "click create", + }, { + trigger: "input[name=name]", + content: "complete name", + run: "text Test Lead 2", + }, { + trigger: "div[name=planned_revenue] > input", + content: "complete planned revenue", + run: "text 999999998", + }, { + trigger: "button.o_kanban_add", + content: "create lead", + }, { + trigger: ".o_kanban_record .o_kanban_record_title:contains('Test Lead 2')", + content: "click on lead", + }, { + trigger: ".o_statusbar_status button[data-value='4']", + content: "move lead to won stage", + }, { + trigger: ".o_statusbar_status button[data-value='1']", + extra_trigger: ".o_reward_rainbow", + content: "move lead to previous stage & rainbowman appears", + }, { + trigger: "button[name=action_set_won_rainbowman]", + content: "click button mark won", + }, { + trigger: ".o_menu_brand", + extra_trigger: ".o_reward_rainbow", + content: "last rainbowman appears", + } + ]); +}); diff --git a/addons/crm/views/assets.xml b/addons/crm/views/assets.xml index e3edab1eee4..1ffed45b2a8 100644 --- a/addons/crm/views/assets.xml +++ b/addons/crm/views/assets.xml @@ -2,8 +2,21 @@ + + diff --git a/addons/crm/views/crm_lead_views.xml b/addons/crm/views/crm_lead_views.xml index cdc95c11ada..2c3357d1d47 100644 --- a/addons/crm/views/crm_lead_views.xml +++ b/addons/crm/views/crm_lead_views.xml @@ -4,7 +4,7 @@ crm.lead.form crm.lead -
+