Created on:
diff --git a/addons/crm/models/crm_lead.py b/addons/crm/models/crm_lead.py
index 1f3daa6d1c9..61c8adfba25 100644
--- a/addons/crm/models/crm_lead.py
+++ b/addons/crm/models/crm_lead.py
@@ -213,7 +213,7 @@ class Lead(models.Model):
automated_probability = fields.Float('Automated Probability', compute='_compute_probabilities', readonly=True, store=True)
is_automated_probability = fields.Boolean('Is automated probability?', compute="_compute_is_automated_probability")
# Won/Lost
- lost_reason = fields.Many2one(
+ lost_reason_id = fields.Many2one(
'crm.lost.reason', string='Lost Reason',
index=True, ondelete='restrict', tracking=True)
# Statistics
@@ -901,7 +901,7 @@ class Lead(models.Model):
activated = self.filtered(lambda lead: lead.active)
archived = self.filtered(lambda lead: not lead.active)
if activated:
- activated.write({'lost_reason': False})
+ activated.write({'lost_reason_id': False})
activated._compute_probabilities()
if archived:
archived.write({'probability': 0, 'automated_probability': 0})
@@ -1355,9 +1355,9 @@ class Lead(models.Model):
'type': lambda fname, leads: 'opportunity' if any(lead.type == 'opportunity' for lead in leads) else 'lead',
'priority': lambda fname, leads: max(leads.mapped('priority')) if leads else False,
'tag_ids': lambda fname, leads: leads.mapped('tag_ids'),
- 'lost_reason': lambda fname, leads:
+ 'lost_reason_id': lambda fname, leads:
False if leads and leads[0].probability
- else next((lead.lost_reason for lead in leads if lead.lost_reason), False),
+ else next((lead.lost_reason_id for lead in leads if lead.lost_reason_id), False),
}
def _merge_get_fields(self):
@@ -1723,7 +1723,7 @@ class Lead(models.Model):
self.ensure_one()
if 'stage_id' in init_values and self.probability == 100 and self.stage_id:
return self.env.ref('crm.mt_lead_won')
- elif 'lost_reason' in init_values and self.lost_reason:
+ elif 'lost_reason_id' in init_values and self.lost_reason_id:
return self.env.ref('crm.mt_lead_lost')
elif 'stage_id' in init_values:
return self.env.ref('crm.mt_lead_stage')
diff --git a/addons/crm/models/crm_lost_reason.py b/addons/crm/models/crm_lost_reason.py
index 4b848acbc5f..714a984db68 100644
--- a/addons/crm/models/crm_lost_reason.py
+++ b/addons/crm/models/crm_lost_reason.py
@@ -13,8 +13,12 @@ class LostReason(models.Model):
leads_count = fields.Integer('Leads Count', compute='_compute_leads_count')
def _compute_leads_count(self):
- lead_data = self.env['crm.lead'].with_context(active_test=False).read_group([('lost_reason', 'in', self.ids)], ['lost_reason'], ['lost_reason'])
- mapped_data = dict((data['lost_reason'][0], data['lost_reason_count']) for data in lead_data)
+ lead_data = self.env['crm.lead'].with_context(active_test=False).read_group(
+ [('lost_reason_id', 'in', self.ids)],
+ ['lost_reason_id'],
+ ['lost_reason_id']
+ )
+ mapped_data = dict((data['lost_reason_id'][0], data['lost_reason_id_count']) for data in lead_data)
for reason in self:
reason.leads_count = mapped_data.get(reason.id, 0)
@@ -22,7 +26,7 @@ class LostReason(models.Model):
return {
'name': _('Leads'),
'view_mode': 'tree,form',
- 'domain': [('lost_reason', 'in', self.ids)],
+ 'domain': [('lost_reason_id', 'in', self.ids)],
'res_model': 'crm.lead',
'type': 'ir.actions.act_window',
'context': {'create': False, 'active_test': False},
diff --git a/addons/crm/report/crm_opportunity_report_views.xml b/addons/crm/report/crm_opportunity_report_views.xml
index 42347272642..8f8a7432ea6 100644
--- a/addons/crm/report/crm_opportunity_report_views.xml
+++ b/addons/crm/report/crm_opportunity_report_views.xml
@@ -105,7 +105,7 @@
-
+
diff --git a/addons/crm/tests/common.py b/addons/crm/tests/common.py
index 71633308398..02b35af3a3f 100644
--- a/addons/crm/tests/common.py
+++ b/addons/crm/tests/common.py
@@ -242,6 +242,10 @@ class TestCrmCommon(TestSalesCommon, MailCase):
'res_id': cls.activity_type_1.id,
})
+ def setUp(self):
+ super(TestCrmCommon, self).setUp()
+ self.flush_tracking()
+
@classmethod
def _activate_multi_company(cls):
cls.company_2 = cls.env['res.company'].create({
diff --git a/addons/crm/tests/test_crm_lead_lost.py b/addons/crm/tests/test_crm_lead_lost.py
index 7f2819f7825..33d5f38ea41 100644
--- a/addons/crm/tests/test_crm_lead_lost.py
+++ b/addons/crm/tests/test_crm_lead_lost.py
@@ -4,9 +4,10 @@
from odoo.addons.crm.tests import common as crm_common
from odoo.exceptions import AccessError
from odoo.tests.common import tagged, users
+from odoo.tools import mute_logger
-@tagged('lead_manage')
+@tagged('lead_manage', 'lead_lost')
class TestLeadConvert(crm_common.TestCrmCommon):
@classmethod
@@ -18,29 +19,95 @@ class TestLeadConvert(crm_common.TestCrmCommon):
@users('user_sales_salesman')
def test_lead_lost(self):
+ """ Test setting a lead as lost using the wizard. Also check that an
+ 'html editor' void content used as feedback is not logged on the lead. """
+ # Initial data
+ self.assertEqual(len(self.lead_1.message_ids), 1, 'Should contain creation message')
+ creation_message = self.lead_1.message_ids[0]
+ self.assertEqual(creation_message.subtype_id, self.env.ref('crm.mt_lead_create'))
+
+ # Update responsible as ACLs is "own only" for user_sales_salesman
self.lead_1.with_user(self.user_sales_manager).write({
'user_id': self.user_sales_salesman.id,
'probability': 32,
})
+ self.flush_tracking()
- lead = self.lead_1.with_user(self.env.user)
+ lead = self.env['crm.lead'].browse(self.lead_1.ids)
+ self.assertFalse(lead.lost_reason_id)
self.assertEqual(lead.probability, 32)
+ self.assertEqual(len(lead.message_ids), 2, 'Should have tracked new responsible')
+ update_message = lead.message_ids[0]
+ self.assertEqual(update_message.subtype_id, self.env.ref('mail.mt_note'))
+ # mark as lost using the wizard
lost_wizard = self.env['crm.lead.lost'].with_context({
'active_ids': lead.ids,
}).create({
- 'lost_reason_id': self.lost_reason.id
+ 'lost_reason_id': self.lost_reason.id,
+ 'lost_feedback': '
', # void content
})
lost_wizard.action_lost_reason_apply()
+ self.flush_tracking()
- self.assertEqual(lead.probability, 0)
- self.assertEqual(lead.automated_probability, 0)
+ # check lead update
self.assertFalse(lead.active)
- self.assertEqual(lead.lost_reason, self.lost_reason) # TDE FIXME: should be called lost_reason_id non didjou
+ self.assertEqual(lead.automated_probability, 0)
+ self.assertEqual(lead.lost_reason_id, self.lost_reason) # TDE FIXME: should be called lost_reason_id non didjou
+ self.assertEqual(lead.probability, 0)
+ # check messages
+ self.assertEqual(len(lead.message_ids), 3, 'Should have logged a tracking message for lost lead with reason')
+ update_message = lead.message_ids[0]
+ self.assertEqual(update_message.subtype_id, self.env.ref('crm.mt_lead_lost'))
+ self.assertEqual(len(update_message.tracking_value_ids), 2, 'Tracking: active, lost reason')
+ self.assertTracking(
+ update_message,
+ [('active', 'boolean', True, False),
+ ('lost_reason_id', 'many2one', False, self.lost_reason)
+ ]
+ )
+
+ @users('user_sales_leads')
+ def test_lead_lost_batch_wfeedback(self):
+ """ Test setting leads as lost in batch using the wizard, including a log
+ message. """
+ leads = self._create_leads_batch(lead_type='lead', count=10, probabilities=[10, 20, 30])
+ self.assertEqual(len(leads), 10)
+ self.flush_tracking()
+
+ lost_wizard = self.env['crm.lead.lost'].with_context({
+ 'active_ids': leads.ids,
+ }).create({
+ 'lost_reason_id': self.lost_reason.id,
+ 'lost_feedback': '
I cannot find it. It was in my closet and pouf, disappeared.
',
+ })
+ lost_wizard.action_lost_reason_apply()
+ self.flush_tracking()
+
+ for lead in leads:
+ # check content
+ self.assertFalse(lead.active)
+ self.assertEqual(lead.automated_probability, 0)
+ self.assertEqual(lead.probability, 0)
+ self.assertEqual(lead.lost_reason_id, self.lost_reason)
+ # check messages
+ self.assertEqual(len(lead.message_ids), 2, 'Should have 2 messages: creation, lost with log')
+ lost_message = lead.message_ids.filtered(lambda msg: msg.subtype_id == self.env.ref('crm.mt_lead_lost'))
+ self.assertTrue(lost_message)
+ self.assertTracking(
+ lost_message,
+ [('active', 'boolean', True, False),
+ ('lost_reason_id', 'many2one', False, self.lost_reason)
+ ]
+ )
+ self.assertIn('
I cannot find it. It was in my closet and pouf, disappeared.
', lost_message.body,
+ 'Feedback should be included directly within tracking message')
@users('user_sales_salesman')
+ @mute_logger('odoo.addons.base.models')
def test_lead_lost_crm_rights(self):
+ """ Test ACLs of lost reasons management and usage """
lead = self.lead_1.with_user(self.env.user)
# nice try little salesman but only managers can create lost reason to avoid bloating the DB
diff --git a/addons/crm/tests/test_crm_lead_merge.py b/addons/crm/tests/test_crm_lead_merge.py
index bbf73b70155..9f5b8bb2dfa 100644
--- a/addons/crm/tests/test_crm_lead_merge.py
+++ b/addons/crm/tests/test_crm_lead_merge.py
@@ -307,7 +307,7 @@ class TestLeadMerge(TestLeadMergeCommon):
# because "lead_1" is not lost
lost_reason = self.env['crm.lost.reason'].create({'name': 'Test Reason'})
self.lead_w_partner.write({
- 'lost_reason': lost_reason,
+ 'lost_reason_id': lost_reason,
'probability': 0,
})
@@ -317,7 +317,7 @@ class TestLeadMerge(TestLeadMergeCommon):
name='Nibbler Spacecraft Request',
partner_id=self.contact_company_1,
priority='2',
- lost_reason=False,
+ lost_reason_id=False,
tag_ids=all_tags):
leads._merge_opportunity(auto_unlink=False, max_length=None)
@@ -331,11 +331,11 @@ class TestLeadMerge(TestLeadMergeCommon):
})
lost_reason = self.env['crm.lost.reason'].create({'name': 'Test Reason'})
- self.lead_w_partner.lost_reason = lost_reason
+ self.lead_w_partner.lost_reason_id = lost_reason
leads = self.env['crm.lead'].browse(self.leads.ids)._sort_by_confidence_level(reverse=True)
- with self.assertLeadMerged(leads[0], leads, lost_reason=lost_reason):
+ with self.assertLeadMerged(leads[0], leads, lost_reason_id=lost_reason):
leads._merge_opportunity(auto_unlink=False, max_length=None)
@users('user_sales_manager')
diff --git a/addons/crm/views/crm_lead_views.xml b/addons/crm/views/crm_lead_views.xml
index 277d07edb80..44127d2899d 100644
--- a/addons/crm/views/crm_lead_views.xml
+++ b/addons/crm/views/crm_lead_views.xml
@@ -246,7 +246,7 @@
-
+
@@ -985,7 +985,7 @@
-
+
diff --git a/addons/crm/wizard/crm_lead_lost.py b/addons/crm/wizard/crm_lead_lost.py
index 8f64a40579b..0db92f5a739 100644
--- a/addons/crm/wizard/crm_lead_lost.py
+++ b/addons/crm/wizard/crm_lead_lost.py
@@ -1,6 +1,8 @@
# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
-from odoo import api, fields, models
+from odoo import fields, models, _
+from odoo.tools.mail import is_html_empty
class CrmLeadLost(models.TransientModel):
@@ -8,7 +10,20 @@ class CrmLeadLost(models.TransientModel):
_description = 'Get Lost Reason'
lost_reason_id = fields.Many2one('crm.lost.reason', 'Lost Reason')
+ lost_feedback = fields.Html(
+ 'Closing Note', sanitize=True,
+ help="Closing note logged in leads discussion history."
+ )
def action_lost_reason_apply(self):
+ self.ensure_one()
leads = self.env['crm.lead'].browse(self.env.context.get('active_ids'))
- return leads.action_set_lost(lost_reason=self.lost_reason_id.id)
+ if not is_html_empty(self.lost_feedback):
+ leads._track_set_log_message(
+ '' % (
+ _('Lost Comment'),
+ self.lost_feedback
+ )
+ )
+ res = leads.action_set_lost(lost_reason_id=self.lost_reason_id.id)
+ return res
diff --git a/addons/crm/wizard/crm_lead_lost_views.xml b/addons/crm/wizard/crm_lead_lost_views.xml
index 79611a98e7c..b046443d5e8 100644
--- a/addons/crm/wizard/crm_lead_lost_views.xml
+++ b/addons/crm/wizard/crm_lead_lost_views.xml
@@ -5,9 +5,10 @@
crm.lead.lost