diff --git a/addons/crm/__manifest__.py b/addons/crm/__manifest__.py
index 9282c7991f0..ea636571ffa 100644
--- a/addons/crm/__manifest__.py
+++ b/addons/crm/__manifest__.py
@@ -33,6 +33,7 @@
'data/crm_stage_data.xml',
'data/crm_team_data.xml',
'data/digest_data.xml',
+ 'data/ir_action_data.xml',
'data/ir_cron_data.xml',
'data/mail_data.xml',
'data/crm_recurring_plan_data.xml',
diff --git a/addons/crm/data/crm_lead_demo.xml b/addons/crm/data/crm_lead_demo.xml
index b831a9dda15..006d2f9cdd8 100644
--- a/addons/crm/data/crm_lead_demo.xml
+++ b/addons/crm/data/crm_lead_demo.xml
@@ -353,7 +353,7 @@ Andrew
]]>
Kensington Road 189
1
-
+
@@ -379,7 +379,7 @@ Andrew]]>
+32 10 588 558
2
-
+
@@ -402,7 +402,7 @@ Andrew]]>
+32 10 588 558
1
-
+
@@ -495,7 +495,7 @@ Andrew]]>
Union Road
2
-
+
@@ -520,7 +520,7 @@ Andrew]]>
Rue des Palais 51, bte 33
1
-
+
@@ -553,7 +553,7 @@ Andrew]]>
1
-
+
@@ -628,6 +628,7 @@ Andrew]]>
+1 312 349 2324
2
+
@@ -684,6 +685,7 @@ Andrew]]>
0
+
@@ -702,6 +704,7 @@ Andrew]]>
Lima
0
+
@@ -750,7 +753,6 @@ Andrew]]>
1300
2
-
@@ -774,7 +776,7 @@ Andrew]]>
Rue de Namur 69
2
-
+
@@ -865,7 +867,7 @@ Andrew]]>
1920 Del Dew Drive
1
-
+
@@ -887,7 +889,7 @@ Andrew]]>
1920 Del Dew Drive
1
-
+
diff --git a/addons/crm/data/ir_action_data.xml b/addons/crm/data/ir_action_data.xml
new file mode 100644
index 00000000000..afd5fa06514
--- /dev/null
+++ b/addons/crm/data/ir_action_data.xml
@@ -0,0 +1,12 @@
+
+
+
+
+ Crm: Forecast
+
+ code
+
+ action = model.action_opportunity_forecast()
+
+
+
diff --git a/addons/crm/models/crm_team.py b/addons/crm/models/crm_team.py
index 3c18597b34c..521c2630721 100644
--- a/addons/crm/models/crm_team.py
+++ b/addons/crm/models/crm_team.py
@@ -587,6 +587,15 @@ class Team(models.Model):
@api.model
def action_your_pipeline(self):
action = self.env["ir.actions.actions"]._for_xml_id("crm.crm_lead_action_pipeline")
+ return self._action_update_to_pipeline(action)
+
+ @api.model
+ def action_opportunity_forecast(self):
+ action = self.env['ir.actions.actions']._for_xml_id('crm.crm_lead_action_forecast')
+ return self._action_update_to_pipeline(action)
+
+ @api.model
+ def _action_update_to_pipeline(self, action):
user_team_id = self.env.user.sale_team_id.id
if user_team_id:
# To ensure that the team is readable in multi company
diff --git a/addons/crm/report/crm_activity_report_views.xml b/addons/crm/report/crm_activity_report_views.xml
index 6b9f1efe030..b76e34d3661 100644
--- a/addons/crm/report/crm_activity_report_views.xml
+++ b/addons/crm/report/crm_activity_report_views.xml
@@ -104,7 +104,7 @@
name="Activities"
parent="crm_menu_report"
action="crm_activity_report_action"
- sequence="3"/>
+ sequence="4"/>
Pipeline Activities
diff --git a/addons/crm/static/src/scss/crm.scss b/addons/crm/static/src/scss/crm.scss
index f3970e4ba69..e731507557b 100644
--- a/addons/crm/static/src/scss/crm.scss
+++ b/addons/crm/static/src/scss/crm.scss
@@ -21,7 +21,7 @@
}
}
- .oe_kanban_card_lost_ribbon {
+ .oe_kanban_card_ribbon {
min-height: 105px;
.o_kanban_record_title {
max-width: calc(100% - 65px);
diff --git a/addons/crm/static/tests/tours/crm_forecast_tour.js b/addons/crm/static/tests/tours/crm_forecast_tour.js
new file mode 100644
index 00000000000..df08fea7171
--- /dev/null
+++ b/addons/crm/static/tests/tours/crm_forecast_tour.js
@@ -0,0 +1,98 @@
+/** @odoo-module */
+import tour from 'web_tour.tour';
+const today = moment();
+
+tour.register('crm_forecast', {
+ test: true,
+ url: "/web",
+}, [
+ tour.stepUtils.showAppsMenuItem(),
+ {
+ trigger: ".o_app[data-menu-xmlid='crm.crm_menu_root']",
+ content: "open crm app",
+ }, {
+ trigger: '.o_dropdown_toggler[data-menu-xmlid="crm.crm_menu_report"]',
+ content: 'Open Reporting menu',
+ run: 'click',
+ }, {
+ trigger: '.o_dropdown_item[data-menu-xmlid="crm.crm_menu_forecast"]',
+ content: 'Open Forecast menu',
+ run: 'click',
+ }, {
+ trigger: '.o_column_quick_create:contains(Add next month)',
+ content: 'Wait page loading'
+ }, {
+ trigger: ".o-kanban-button-new",
+ content: "click create",
+ run: 'click',
+ }, {
+ trigger: "input[name=name]",
+ content: "complete name",
+ run: "text Test Opportunity 1",
+ }, {
+ trigger: "div[name=expected_revenue] > input",
+ content: "complete expected revenue",
+ run: "text 999999",
+ }, {
+ trigger: "button.o_kanban_edit",
+ content: "edit lead",
+ }, {
+ trigger: "input[name=date_deadline]",
+ content: "complete expected closing",
+ run: `text ${today.format("MM/DD/YYYY")}`,
+ }, {
+ trigger: "input[name=date_deadline]",
+ content: "click to make the datepicker disappear",
+ run: "click"
+ }, {
+ trigger: "body:not(:has(div.bootstrap-datetimepicker-widget))",
+ content: "wait for date_picker to disappear",
+ run: function () {},
+ }, {
+ trigger: '.o_back_button',
+ content: 'navigate back to the kanban view',
+ position: "bottom",
+ run: "click"
+ }, {
+ trigger: ".o_kanban_record .o_kanban_record_title:contains('Test Opportunity 1')",
+ content: "move to the next month",
+ run: function (actions) {
+ const undefined_groups = $('.o_column_title:contains("Undefined")').length;
+ actions.drag_and_drop(` .o_opportunity_kanban .o_kanban_group:eq(${1 + undefined_groups})`, this.$anchor);
+ }
+ }, {
+ trigger: ".o_kanban_record .o_kanban_record_title:contains('Test Opportunity 1')",
+ content: "edit lead",
+ run: "click"
+ }, {
+ trigger: `span[name=date_deadline]:contains("${moment(today).add(2, 'months').startOf('month').subtract(1, 'days').format("MM/DD/YYYY")}")`,
+ content: "edit datetime",
+ position: "bottom",
+ run: "click"
+ }, {
+ trigger: "input[name=date_deadline]",
+ content: "complete expected closing",
+ run: `text ${moment(today).add(5, 'months').startOf('month').subtract(1, 'days').format("MM/DD/YYYY")}`
+ }, {
+ trigger: "body:not(:has(div.bootstrap-datetimepicker-widget))",
+ content: "wait for date_picker to disappear",
+ run: function () {},
+ }, {
+ trigger: "input[name=probability]",
+ content: "max out probability",
+ run: "text 100"
+ }, {
+ trigger: '.o_back_button',
+ content: 'navigate back to the kanban view',
+ position: "bottom",
+ run: "click"
+ }, {
+ trigger: '.o_kanban_add_column',
+ content: "add next month",
+ run: "click"
+ }, {
+ trigger: ".o_kanban_record:contains('Test Opportunity 1'):contains('Won')",
+ content: "assert that the opportunity has the Won banner",
+ run: function () {},
+ }
+]);
diff --git a/addons/crm/tests/test_crm_ui.py b/addons/crm/tests/test_crm_ui.py
index 04aa368bc1a..4cca0e9fb73 100644
--- a/addons/crm/tests/test_crm_ui.py
+++ b/addons/crm/tests/test_crm_ui.py
@@ -25,6 +25,8 @@ class TestUi(HttpCase):
})
self.start_tour("/web", 'crm_rainbowman', login="temp_crm_user")
+ def test_03_crm_tour_forecast(self):
+ self.start_tour("/web", 'crm_forecast', login="admin")
class TestCRMLeadMisc(TestCrmCommon):
diff --git a/addons/crm/views/crm_lead_views.xml b/addons/crm/views/crm_lead_views.xml
index 19c39c3fa52..5d5913539a0 100644
--- a/addons/crm/views/crm_lead_views.xml
+++ b/addons/crm/views/crm_lead_views.xml
@@ -535,7 +535,7 @@
-
+
Lost
@@ -591,6 +591,59 @@
+
+ crm.lead.view.kanban.forecast
+ crm.lead
+
+ primary
+ 32
+
+
+ date_deadline
+ forecast_kanban
+
+
+
+
+
+
+
+
+ prorated_revenue
+
+
+
+
+
+
+ #{!selection_mode ? kanban_color(record.color.raw_value) : ''}
+ #{lost_ribbon || won_ribbon ? 'oe_kanban_card_ribbon' : ''}
+ oe_kanban_global_click oe_kanban_card d-flex flex-column
+
+
+
+
+ Won
+ Lost
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
crm.lead.search.lead
crm.lead
@@ -738,6 +791,25 @@
+
+ crm.lead.view.tree.forecast
+ crm.lead
+
+ primary
+ 32
+
+
+ forecast_list
+
+
+ hide
+
+
+
+
+
+
+
crm.lead.list.activities
crm.lead
@@ -797,6 +869,24 @@
+
+ crm.lead.view.graph.forecast
+ crm.lead
+ 32
+
+
+
+
+
+
+
+
+
+
+
+
+
+
crm.lead.view.pivot
crm.lead
@@ -811,6 +901,25 @@
+
+ crm.lead.view.pivot.forecast
+ crm.lead
+ 32
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
crm.lead.search.opportunity
crm.lead
@@ -887,6 +996,24 @@
+
+ crm.lead.view.search.forecast
+ crm.lead
+
+ primary
+ 32
+
+
+
+
+
+
+
+
+
+
+
+
@@ -1095,9 +1222,62 @@ if record:
+
+ Forecast
+ crm.lead
+ kanban,graph,pivot,tree,form
+ [('type', '=', 'opportunity')]
+ {
+ 'default_type': 'opportunity',
+ 'search_default_assigned_to_me': 1,
+ 'search_default_forecast': 1,
+ 'search_default_date_deadline': 1,
+ 'forecast_field': 'date_deadline'
+ }
+
+
+ No opportunity to display!
+
+ Easily set expected closing dates and overview your revenue streams.
+
+
+
+
+
+
+
+ kanban
+
+
+
+
+
+
+ graph
+
+
+
+
+
+
+ pivot
+
+
+
+
+
+
+ tree
+
+
+
+
+
diff --git a/addons/crm/views/crm_menu_views.xml b/addons/crm/views/crm_menu_views.xml
index 1f0aca1f5af..14182af8d87 100644
--- a/addons/crm/views/crm_menu_views.xml
+++ b/addons/crm/views/crm_menu_views.xml
@@ -60,17 +60,22 @@
parent="crm_menu_root"
sequence="20"
groups="sales_team.group_sale_salesman"/>
+
+ sequence="2"/>
+ sequence="3"/>