[IMP] crm_*: add more fields when we merge multiple leads

Purpose
=======
Add more fields when we merge multiple leads to be sure not to lose
valuable information.

Specifications
==============
New fields merged
-----------------
Those fields are now propagated to the destination,
- referred
- color
- recurring_revenue
- recurring_plan
- function
- lang_id
- date_deadline
- reveal_id
- lead_mining_request_id
- reveal_ip
- reveal_iap_credits
- reveal_rule_id
- event_lead_rule_id
- event_id
- lost_reason (if the destination lead is lost)
- iap_enrich_done
- tag_ids

The address is taken from the lead with the most non-empty address
fields (sorted by highest rank if multiple leads have the same amount
of non-empty fields).

Followers
---------
When merging multiple leads, we want to move the followers to the
destination lead if they posted a message in the last 30 days.

A message will be added in the merge note to know which followers have
been added.

Views
-----
Remove the "groups" attribute  on the recurring revenue
fields. Instead we hide them directly in the views.

Task-2447721

closes odoo/odoo#75742

Signed-off-by: awa-odoo <awa-odoo@users.noreply.github.com>
This commit is contained in:
Odoo's Mergebot
2021-10-19 17:58:59 +02:00
committed by GitHub
18 changed files with 470 additions and 32 deletions
+41 -2
View File
@@ -40,15 +40,34 @@
<div t-if="lead.priority">
Priority: <span t-field="lead.priority"/>
</div>
<div t-if="lead.lost_reason">
Lost Reason: <span t-field="lead.lost_reason"/>
</div>
<div>
Created on: <span t-field="lead.create_date"/>
</div>
<div t-if="lead.date_action_last">
Last Action: <span t-field="lead.date_action_last"/>
</div>
<div t-if="lead.date_deadline">
Expected Closing: <span t-field="lead.date_deadline"/>
</div>
<div t-if="not is_html_empty(lead.description)">
Notes: <span t-field="lead.description"/>
</div>
<div t-if="lead.lang_id" name="lang_id">
Language: <span t-field="lead.lang_id"/>
</div>
<div t-if="lead.referred" name="referred">
Referred By: <span t-field="lead.referred"/>
</div>
<div t-if="lead.tag_ids" name="tag_ids" class="d-flex flex-row">
Tags:
<div class="ml-2 d-flex flex-row">
<div t-foreach="lead.tag_ids" t-as="tag" t-esc="tag.name"
t-attf-class="badge badge-pill o_tag_color_#{tag.color} d-inline-block"/>
</div>
</div>
<div t-if="lead.user_id" class="mt-3">
Salesperson: <span t-field="lead.user_id"/>
</div>
@@ -85,10 +104,14 @@
<div t-if="lead.website">
Website: <span t-field="lead.website"/>
</div>
<div t-if="lead.function">
Job Position: <span t-field="lead.function"/>
</div>
</div>
</div>
<div class="mt-3"
t-if="lead.street or lead.street2 or lead.zip or lead.city or lead.state_id or lead.country_id">
t-if="lead.street or lead.street2 or lead.zip or lead.city or lead.state_id or lead.country_id"
name="address">
<div class="font-weight-bold">
Address:
</div>
@@ -99,7 +122,7 @@
<div t-if="lead.state_id" t-field="lead.state_id"/>
<div t-if="lead.country_id" t-field="lead.country_id"/>
</div>
<div class="mt-3 mb-3" name="marketing"
<div class="mt-3" name="marketing"
t-if="lead.campaign_id or lead.medium_id or lead.source_id">
<div class="font-weight-bold">
Marketing:
@@ -114,6 +137,22 @@
Source: <span t-field="lead.source_id"/>
</div>
</div>
<t t-set="lead_followers" t-value="merged_followers and merged_followers.get(lead.id)"/>
<div class="mt-3 mb-3" name="merged_followers" t-if="lead_followers">
<div>
The contacts below have been added as followers of this lead
because they have been contacted less than 30 days ago on
<span class="font-weight-bold" t-esc="lead.name"/>.
</div>
<ul>
<li t-foreach="lead_followers" t-as="follower">
<t t-esc="follower.partner_id.name"/>
<t t-if="follower.partner_id.email">
(<t t-esc="follower.partner_id.email"/>)
</t>
</li>
</ul>
</div>
</blockquote>
</t>
</div>
+84 -15
View File
@@ -15,7 +15,7 @@ from odoo.addons.phone_validation.tools import phone_validation
from odoo.exceptions import UserError, AccessError
from odoo.osv import expression
from odoo.tools.translate import _
from odoo.tools import date_utils, email_re, email_split, is_html_empty
from odoo.tools import date_utils, email_re, email_split, is_html_empty, groupby
from . import crm_stage
@@ -33,31 +33,31 @@ CRM_LEAD_FIELDS_TO_MERGE = [
# description
'name',
'user_id',
'color',
'company_id',
'lang_id',
'team_id',
'referred',
# pipeline
'stage_id',
# revenues
'expected_revenue',
'recurring_plan',
'recurring_revenue',
# dates
'create_date',
'date_action_last',
'date_deadline',
# partner / contact
'partner_id',
'title',
'partner_name',
'contact_name',
'email_from',
'function',
'mobile',
'phone',
'website',
# address
'street',
'street2',
'zip',
'city',
'state_id',
'country_id',
]
# Subset of partner fields: sync any of those
@@ -146,14 +146,12 @@ class Lead(models.Model):
# Revenues
expected_revenue = fields.Monetary('Expected Revenue', currency_field='company_currency', tracking=True)
prorated_revenue = fields.Monetary('Prorated Revenue', currency_field='company_currency', store=True, compute="_compute_prorated_revenue")
recurring_revenue = fields.Monetary('Recurring Revenues', currency_field='company_currency', groups="crm.group_use_recurring_revenues", tracking=True)
recurring_plan = fields.Many2one('crm.recurring.plan', string="Recurring Plan", groups="crm.group_use_recurring_revenues")
recurring_revenue = fields.Monetary('Recurring Revenues', currency_field='company_currency', tracking=True)
recurring_plan = fields.Many2one('crm.recurring.plan', string="Recurring Plan")
recurring_revenue_monthly = fields.Monetary('Expected MRR', currency_field='company_currency', store=True,
compute="_compute_recurring_revenue_monthly",
groups="crm.group_use_recurring_revenues")
compute="_compute_recurring_revenue_monthly")
recurring_revenue_monthly_prorated = fields.Monetary('Prorated MRR', currency_field='company_currency', store=True,
compute="_compute_recurring_revenue_monthly_prorated",
groups="crm.group_use_recurring_revenues")
compute="_compute_recurring_revenue_monthly_prorated")
company_currency = fields.Many2one("res.currency", string='Currency', related='company_id.currency_id', readonly=True)
# Dates
date_closed = fields.Datetime('Closed Date', readonly=True, copy=False)
@@ -1226,6 +1224,7 @@ class Lead(models.Model):
if fnames is None:
fnames = self._merge_get_fields()
fcallables = self._merge_get_fields_specific()
address_values = self._merge_get_fields_address()
# helpers
def _get_first_not_null(attr, opportunities):
@@ -1246,6 +1245,8 @@ class Lead(models.Model):
fcallable = fcallables.get(field_name)
if fcallable and callable(fcallable):
data[field_name] = fcallable(field_name, self)
elif field_name in address_values:
data[field_name] = address_values[field_name]
elif not fcallable and field.type in ('many2many', 'one2many'):
continue
else:
@@ -1295,10 +1296,13 @@ class Lead(models.Model):
if team_id:
merged_data['team_id'] = team_id
merged_followers = opportunities_head._merge_followers(opportunities_tail)
# log merge message
opportunities_head.message_post_with_view(
"crm.crm_lead_merge_summary",
values={
"merged_followers": merged_followers,
"opportunities": opportunities_tail,
"is_html_empty": is_html_empty
},
@@ -1323,15 +1327,36 @@ class Lead(models.Model):
return opportunities_head
def _merge_get_fields_address(self):
"""The address fields are propagated as a whole.
The address is taken from the lead with the most non-empty address field
(sorted by highest rank if multiple lead have the same amount of non-empty
fields).
"""
source_lead = max(self, key=lambda lead: len(list(
lead[field] for field in PARTNER_ADDRESS_FIELDS_TO_SYNC
if lead[field]
)))
return {fname: source_lead[fname] for fname in PARTNER_ADDRESS_FIELDS_TO_SYNC}
def _merge_get_fields_specific(self):
return {
'description': lambda fname, leads: '<br/><br/>'.join(desc for desc in leads.mapped('description') if not is_html_empty(desc)),
'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:
False if leads and leads[0].probability
else next((lead.lost_reason for lead in leads if lead.lost_reason), False),
}
def _merge_get_fields(self):
return list(CRM_LEAD_FIELDS_TO_MERGE) + list(self._merge_get_fields_specific().keys())
return (
CRM_LEAD_FIELDS_TO_MERGE
+ list(self._merge_get_fields_specific().keys())
+ PARTNER_ADDRESS_FIELDS_TO_SYNC
)
def _merge_dependences(self, opportunities):
""" Merge dependences (messages, attachments,activities, calendar events,
@@ -1412,6 +1437,50 @@ class Lead(models.Model):
'opportunity_id': self.id,
})
def _merge_followers(self, opportunities):
"""Add the followers into the destination lead if they post a message in the last 30 days.
:param opportunities : Record<crm.lead> of opportunities to transfer
:return: {old_lead_id: Record<mail.followers>} Followers which have been added in
the destination lead grouped by source lead ID.
"""
self.ensure_one()
self.env['mail.message'].flush()
self.env['mail.followers'].flush()
# Get the active followers (followers whose partner post a message on the
# leads in the last 30 days) which should be moved on the destination lead
self.env.cr.execute(
'''
SELECT MAX(mf.id) AS id
FROM mail_followers AS mf
JOIN mail_message AS mm
ON mm.author_id = mf.partner_id
AND mm.res_id = mf.res_id
AND mm.model = 'crm.lead'
AND mm.date > NOW() - INTERVAL '30 DAY'
/* Check if the partner is already
following the destination lead */
LEFT JOIN mail_followers AS destf
ON destf.res_model = 'crm.lead'
AND destf.res_id = %(lead_id)s
AND destf.partner_id = mf.partner_id
/* Select only once each partner
to not create duplicated followers */
WHERE mf.res_model = 'crm.lead'
AND mf.res_id IN %(lead_ids)s
AND destf IS NULL
GROUP BY mf.partner_id
''',
{'lead_ids': tuple(opportunities.ids), 'lead_id': self.id},
)
followers_to_update = [r[0] for r in self.env.cr.fetchall()]
followers_to_update = self.env['mail.followers'].browse(followers_to_update).sudo()
followers_by_old_lead = dict(groupby(followers_to_update, lambda f: f.res_id))
followers_to_update.write({'res_id': self.id})
return followers_by_old_lead
# CONVERT
# ----------------------------------------------------------------------
+31 -1
View File
@@ -36,4 +36,34 @@
blockquote {
font-style: normal;
}
}
// Tag colors
@for $size from 1 through length($o-colors) {
.o_tag_color_#{$size - 1} {
border: transparent;
line-height: normal;
$background-color: white;
// no color selected
@if $size == 1 {
& {
color: black;
background-color: $background-color;
box-shadow: inset 0 0 0 1px nth($o-colors, $size);
}
} @else {
$background-color: nth($o-colors, $size);
& {
color: white;
background-color: $background-color;
}
}
@at-root a#{&} {
&:hover {
color: color-yiq($background-color);
background-color: darken($background-color, 10%);
}
}
}
}
}
+12 -2
View File
@@ -5,7 +5,7 @@ from ast import literal_eval
from contextlib import contextmanager
from unittest.mock import patch
from odoo.addons.crm.models.crm_lead import CRM_LEAD_FIELDS_TO_MERGE
from odoo.addons.crm.models.crm_lead import PARTNER_ADDRESS_FIELDS_TO_SYNC
from odoo.addons.mail.tests.common import MailCase, mail_new_test_user
from odoo.addons.phone_validation.tools import phone_validation
from odoo.addons.sales_team.tests.common import TestSalesCommon
@@ -423,7 +423,10 @@ class TestCrmCommon(TestSalesCommon, MailCase):
finally:
# support specific values caller may want to check in addition to generic tests
for fname, expected in expected.items():
self.assertEqual(opportunity[fname], expected)
if expected is False:
self.assertFalse(opportunity[fname], "%s must be False" % fname)
else:
self.assertEqual(opportunity[fname], expected, "%s must be equal to %s" % (fname, expected))
# classic fields: first not void wins or specific computation
for fname in fields_all:
@@ -438,6 +441,9 @@ class TestCrmCommon(TestSalesCommon, MailCase):
self.assertEqual(opp_value, _get_priority())
elif fname in ('order_ids', 'visitor_ids'):
self.assertEqual(opp_value, _aggregate(fname))
elif fname in PARTNER_ADDRESS_FIELDS_TO_SYNC:
# Specific computation, has its own test
continue
else:
self.assertEqual(
opp_value if opp_value or not isinstance(opp_value, models.BaseModel) else False,
@@ -607,6 +613,9 @@ class TestLeadConvertMassCommon(TestLeadConvertCommon):
'partner_id': cls.contact_1.id,
})
cls.lead_w_partner.write({'stage_id': False})
cls.tags = cls.env['crm.tag'].create([{'name': 'Tag %i' % i} for i in range(4)])
cls.lead_1.tag_ids = cls.tags[:3]
cls.lead_w_partner_company = cls.env['crm.lead'].create({
'name': 'New1',
'type': 'lead',
@@ -616,6 +625,7 @@ class TestLeadConvertMassCommon(TestLeadConvertCommon):
'partner_id': cls.contact_company_1.id,
'contact_name': 'Hermes Conrad',
'email_from': 'hermes.conrad@test.example.com',
'tag_ids': (cls.tags[:2] | cls.tags[3]),
})
cls.lead_w_contact = cls.env['crm.lead'].create({
'name': 'LeadContact',
+193 -1
View File
@@ -2,6 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import base64
from datetime import timedelta
from odoo.addons.crm.tests.common import TestLeadConvertMassCommon
from odoo.fields import Datetime
@@ -84,6 +85,73 @@ class TestLeadMerge(TestLeadMergeCommon):
self.assertEqual(self.lead_w_email_lost.user_id, self.env['res.users'])
self.assertEqual(self.lead_w_email_lost.team_id, self.sales_team_1)
@users('user_sales_manager')
@mute_logger('odoo.models.unlink')
def test_lead_merge_address_not_propagated(self):
"""All addresses have the same number of non-empty address fields, take the first one (lead_w_contact)
because it's the lead that has the best confidence level after being sorted with '_sort_by_confidence_level'"""
initial_address = {
'street': 'Test street',
'street2': 'Test street2',
'city': 'Test City',
'zip': '5000',
'state_id': False,
'country_id': self.env.ref('base.be'),
}
self.lead_w_contact.write(initial_address)
(self.leads - self.lead_w_contact).write({
'street': 'Other street',
'street2': 'Other street2',
'city': 'Other City',
'zip': '6666',
'state_id': self.env.ref('base.state_us_1'),
'country_id': False,
})
leads = self.env['crm.lead'].browse(self.leads.ids)._sort_by_confidence_level(reverse=True)
with self.assertLeadMerged(self.lead_w_contact, leads, **initial_address):
leads._merge_opportunity(auto_unlink=False, max_length=None)
@users('user_sales_manager')
@mute_logger('odoo.models.unlink')
def test_lead_merge_address_propagated(self):
"""Test that the address with the most non-empty fields is propagated.
Should take the address of "lead_w_partner" (maximum number of non-empty address
fields and with an highest rank than "lead_w_email_lost")
"""
self.leads.write({
'street': 'Original street',
'street2': False,
'city': False,
'zip': False,
'state_id': False,
'country_id': False,
})
new_address = {
'street': 'New street',
'street2': False,
'city': 'New City',
'zip': False,
'state_id': False,
'country_id': False,
}
self.lead_w_partner.write(new_address)
self.lead_w_email_lost.write({
'street': 'Other street',
'street2': False,
'city': 'Other City',
'zip': False,
'state_id': False,
'country_id': False,
})
leads = self.env['crm.lead'].browse(self.leads.ids)._sort_by_confidence_level(reverse=True)
with self.assertLeadMerged(self.lead_w_contact, leads, **new_address):
leads._merge_opportunity(auto_unlink=False, max_length=None)
@users('user_sales_manager')
@mute_logger('odoo.models.unlink')
def test_lead_merge_internals(self):
@@ -234,10 +302,40 @@ class TestLeadMerge(TestLeadMergeCommon):
# ensure initial data
(self.lead_w_partner_company | self.lead_1).write({'type': 'opportunity', 'probability': 50})
leads = self.env['crm.lead'].browse(self.leads.ids)._sort_by_confidence_level(reverse=True)
# lead_w_partner is lost, check that the "lost_reason" is not propagated
# 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,
'probability': 0,
})
all_tags = self.leads.mapped('tag_ids')
with self.assertLeadMerged(self.lead_1, leads,
name='Nibbler Spacecraft Request',
partner_id=self.contact_company_1,
priority='2'):
priority='2',
lost_reason=False,
tag_ids=all_tags):
leads._merge_opportunity(auto_unlink=False, max_length=None)
@users('user_sales_manager')
@mute_logger('odoo.models.unlink')
def test_merge_method_propagate_lost_reason(self):
"""Check that the lost reason is propagated to the final lead if it's lost."""
self.leads.write({
'probability': 0,
'automated_probability': 50, # Do not automatically update the probability
})
lost_reason = self.env['crm.lost.reason'].create({'name': 'Test Reason'})
self.lead_w_partner.lost_reason = 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):
leads._merge_opportunity(auto_unlink=False, max_length=None)
@users('user_sales_manager')
@@ -294,3 +392,97 @@ class TestLeadMerge(TestLeadMergeCommon):
# 2many accessors updated
self.assertEqual(master_lead.activity_ids, activity)
self.assertEqual(master_lead.calendar_event_ids, calendar_event)
@users('user_sales_manager')
@mute_logger('odoo.models.unlink')
def test_merge_method_followers(self):
""" Test that the followers of the leads are added in the destination lead.
They should be added if:
- The related partner was active on the lead (posted a message in the last 30 days)
- The related partner is not already following the destination lead
Leads Followers Info
---------------------------------------------------------------------------------
lead_w_contact contact_1 OK (destination lead)
lead_w_email contact_1 KO (already following the destination lead)
contact_2 OK (active on lead_w_email)
contact_company KO (most recent message on lead_w_email is 35 days ago, message
on lead_w_partner is not counted as he doesn't follow it)
lead_w_partner contact_2 KO (already added with lead_w_email)
lead_w_partner_company
"""
self.leads.message_follower_ids.unlink()
self.leads.message_ids.unlink()
self.lead_w_contact.message_subscribe([self.contact_1.id])
self.lead_w_email.message_subscribe([self.contact_1.id, self.contact_2.id, self.contact_company.id])
self.lead_w_partner.message_subscribe([self.contact_2.id])
self.env['mail.message'].create([{
'author_id': self.contact_1.id,
'model': 'crm.lead',
'res_id': self.lead_w_contact.id,
'date': Datetime.now() - timedelta(days=1),
'subtype_id': self.ref('mail.mt_comment'),
'reply_to': False,
'body': 'Test follower',
}, {
'author_id': self.contact_1.id,
'model': 'crm.lead',
'res_id': self.lead_w_email.id,
'date': Datetime.now() - timedelta(days=20),
'subtype_id': self.ref('mail.mt_comment'),
'reply_to': False,
'body': 'Test follower',
}, {
'author_id': self.contact_2.id,
'model': 'crm.lead',
'res_id': self.lead_w_email.id,
'date': Datetime.now() - timedelta(days=15),
'subtype_id': self.ref('mail.mt_comment'),
'reply_to': False,
'body': 'Test follower',
}, {
'author_id': self.contact_2.id,
'model': 'crm.lead',
'res_id': self.lead_w_partner.id,
'date': Datetime.now() - timedelta(days=29),
'subtype_id': self.ref('mail.mt_comment'),
'reply_to': False,
'body': 'Test follower',
}, {
'author_id': self.contact_company.id,
'model': 'crm.lead',
'res_id': self.lead_w_email.id,
'date': Datetime.now() - timedelta(days=35),
'subtype_id': self.ref('mail.mt_comment'),
'reply_to': False,
'body': 'Test follower',
}, {
'author_id': self.contact_company.id,
'model': 'crm.lead',
'res_id': self.lead_w_partner.id,
'date': Datetime.now(),
'subtype_id': self.ref('mail.mt_comment'),
'reply_to': False,
'body': 'Test follower',
}])
initial_followers = self.lead_w_contact.message_follower_ids
leads = self.env['crm.lead'].browse(self.leads.ids)._sort_by_confidence_level(reverse=True)
master_lead = leads._merge_opportunity(max_length=None)
self.assertEqual(master_lead, self.lead_w_contact)
# Check followers of the destination lead
new_partner_followers = (master_lead.message_follower_ids - initial_followers).partner_id
self.assertIn(self.contact_2, new_partner_followers,
'The partner must follow the destination lead')
# "contact_company" posted a message 35 days ago on lead_2, so it's considered as inactive
# "contact_company" posted a message now on lead_3, but he doesn't follow lead_3
# so this message is just ignored
self.assertNotIn(self.contact_company, new_partner_followers,
'The partner was not active on the lead')
self.assertIn(self.contact_1, master_lead.message_follower_ids.partner_id,
'Should not have removed follower of the destination lead')
+13 -11
View File
@@ -462,7 +462,7 @@
<field name="expected_revenue" class="oe_inline mr-5" widget="monetary" options="{'currency_field': 'company_currency'}"/>
<field name="priority" class="oe_inline" nolabel="1" widget="priority"/>
</div>
<div class="o_row">
<div class="o_row" groups="crm.group_use_recurring_revenues">
<field name="recurring_revenue" class="oe_inline pr-4" widget="monetary" options="{'currency_field': 'company_currency'}"/>
<field name="recurring_plan" class="oe_inline" placeholder="E.g. Monthly"
attrs="{'required': [('recurring_revenue', '!=', 0)]}" options="{'no_create': True, 'no_open': True}"/>
@@ -559,11 +559,11 @@
<div class="o_kanban_record_subtitle">
<t t-if="record.expected_revenue.raw_value">
<field name="expected_revenue" widget="monetary" options="{'currency_field': 'company_currency'}"/>
<span t-if="record.recurring_revenue and record.recurring_revenue.raw_value"> + </span>
<span t-if="record.recurring_revenue and record.recurring_revenue.raw_value" groups="crm.group_use_recurring_revenues"> + </span>
</t>
<t t-if="record.recurring_revenue and record.recurring_revenue.raw_value">
<field name="recurring_revenue" widget="monetary" options="{'currency_field': 'company_currency'}"/>
<field name="recurring_plan"/>
<field name="recurring_revenue" widget="monetary" options="{'currency_field': 'company_currency'}" groups="crm.group_use_recurring_revenues"/>
<field name="recurring_plan" groups="crm.group_use_recurring_revenues"/>
</t>
</div>
<div>
@@ -634,11 +634,11 @@
<div class="o_kanban_record_subtitle">
<t t-if="record.prorated_revenue.raw_value">
<field name="prorated_revenue" widget="monetary" options="{'currency_field': 'company_currency'}"/>
<span t-if="record.recurring_revenue and record.recurring_revenue.raw_value"> + </span>
<span t-if="record.recurring_revenue and record.recurring_revenue.raw_value" groups="crm.group_use_recurring_revenues"> + </span>
</t>
<t t-if="record.recurring_revenue and record.recurring_revenue.raw_value">
<field name="recurring_revenue" widget="monetary" options="{'currency_field': 'company_currency'}"/>
<field name="recurring_plan"/>
<field name="recurring_revenue" widget="monetary" options="{'currency_field': 'company_currency'}" groups="crm.group_use_recurring_revenues"/>
<field name="recurring_plan" groups="crm.group_use_recurring_revenues"/>
</t>
</div>
</xpath>
@@ -772,9 +772,11 @@
<field name="source_id" optional="hide"/>
<field name="company_currency" invisible="1"/>
<field name="expected_revenue" sum="Expected Revenues" optional="show" widget="monetary" options="{'currency_field': 'company_currency'}"/>
<field name="recurring_revenue_monthly" sum="Expected MRR" optional="show" widget="monetary" options="{'currency_field': 'company_currency'}"/>
<field name="recurring_revenue" sum="Recurring Revenue" optional="hide" widget="monetary" options="{'currency_field': 'company_currency'}"/>
<field name="recurring_plan" optional="hide"/>
<field name="recurring_revenue_monthly" sum="Expected MRR" optional="show" widget="monetary"
options="{'currency_field': 'company_currency'}" groups="crm.group_use_recurring_revenues"/>
<field name="recurring_revenue" sum="Recurring Revenue" optional="hide" widget="monetary"
options="{'currency_field': 'company_currency'}" groups="crm.group_use_recurring_revenues"/>
<field name="recurring_plan" optional="hide" groups="crm.group_use_recurring_revenues"/>
<field name="stage_id" optional="show" decoration-bf="1"/>
<field name="active" invisible="1"/>
<field name="probability" string="Probability (%)" optional="hide"/>
@@ -896,7 +898,7 @@
<field name="create_date" interval="month" type="row"/>
<field name="stage_id" type="col"/>
<field name="expected_revenue" type="measure"/>
<field name="recurring_revenue_monthly" type="measure"/>
<field name="recurring_revenue_monthly" type="measure" groups="crm.group_use_recurring_revenues"/>
<field name="color" invisible="1"/>
</pivot>
</field>
+6
View File
@@ -159,3 +159,9 @@ class Lead(models.Model):
values=template_values,
subtype_id=self.env.ref('mail.mt_note').id
)
def _merge_get_fields_specific(self):
return {
** super(Lead, self)._merge_get_fields_specific(),
'iap_enrich_done': lambda fname, leads: any(lead.iap_enrich_done for lead in leads),
}
+1
View File
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import test_crm_lead_merge
from . import test_lead_enrich
@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.addons.crm.tests.test_crm_lead_merge import TestLeadMergeCommon
from odoo.tests.common import tagged, users
from odoo.tools import mute_logger
@tagged('lead_manage')
class TestLeadMerge(TestLeadMergeCommon):
@users('user_sales_manager')
@mute_logger('odoo.models.unlink')
def test_merge_method_iap_enrich_done(self):
"""Test that the "iap_enrich_done" is set to True if at least one lead have this value True"""
self.leads.iap_enrich_done = False
self.lead_w_contact.write({
'reveal_id': 'test_reveal_id',
'iap_enrich_done': True,
})
leads = self.env['crm.lead'].browse(self.leads.ids)._sort_by_confidence_level(reverse=True)
with self.assertLeadMerged(leads[0], leads, iap_enrich_done=True, reveal_id='test_reveal_id'):
leads._merge_opportunity(auto_unlink=False, max_length=None)
+3
View File
@@ -8,3 +8,6 @@ class Lead(models.Model):
_inherit = 'crm.lead'
lead_mining_request_id = fields.Many2one('crm.iap.lead.mining.request', string='Lead Mining Request', index=True)
def _merge_get_fields(self):
return super(Lead, self)._merge_get_fields() + ['lead_mining_request_id']
+1
View File
@@ -11,6 +11,7 @@
'data': [
'security/event_crm_security.xml',
'security/ir.model.access.csv',
'data/crm_lead_merge_template.xml',
'views/crm_lead_views.xml',
'views/event_registration_views.xml',
'views/event_lead_rule_views.xml',
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="crm_lead_merge_summary_inherit_event_crm" inherit_id="crm.crm_lead_merge_summary">
<xpath expr="//div[@name='marketing']" position="after">
<div class="mt-3 mb-3" name="event" t-if="lead.event_lead_rule_id or lead.event_id">
<div class="font-weight-bold">
Event:
</div>
<div t-if="lead.event_id">
Event: <span t-field="lead.event_id"/>
</div>
<div t-if="lead.event_lead_rule_id">
Registration Rule: <span t-field="lead.event_lead_rule_id"/>
</div>
</div>
</xpath>
</template>
</odoo>
+3
View File
@@ -30,3 +30,6 @@ class Lead(models.Model):
self.sudo().write({
'registration_ids': [(4, registration.id) for registration in opportunities.sudo().registration_ids]
})
def _merge_get_fields(self):
return super(Lead, self)._merge_get_fields() + ['event_lead_rule_id', 'event_id']
+3
View File
@@ -8,3 +8,6 @@ class Lead(models.Model):
_inherit = 'crm.lead'
reveal_id = fields.Char(string='Reveal ID', help="Technical ID of reveal request done by IAP.")
def _merge_get_fields(self):
return super(Lead, self)._merge_get_fields() + ['reveal_id']
+1
View File
@@ -19,6 +19,7 @@ modules.
'depends': ['sale_management', 'crm'],
'data': [
'security/ir.model.access.csv',
'data/crm_lead_merge_template.xml',
'views/partner_views.xml',
'views/sale_order_views.xml',
'views/crm_lead_views.xml',
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="crm_lead_merge_summary_inherit_sale_crm" inherit_id="crm.crm_lead_merge_summary">
<xpath expr="//blockquote/*[last()]" position="after">
<div t-if="lead.order_ids">
<br/>
<div class="font-weight-bold">
Sale Orders
</div>
<ul>
<li t-foreach="lead.order_ids" t-as="order" t-esc="order.name"/>
</ul>
</div>
</xpath>
</template>
</odoo>
@@ -10,3 +10,6 @@ class Lead(models.Model):
reveal_ip = fields.Char(string='IP Address')
reveal_iap_credits = fields.Integer(string='IAP Credits')
reveal_rule_id = fields.Many2one('crm.reveal.rule', string='Lead Generation Rule', index=True)
def _merge_get_fields(self):
return super(Lead, self)._merge_get_fields() + ['reveal_ip', 'reveal_iap_credits', 'reveal_rule_id']
@@ -11,6 +11,17 @@
Partner Assignment Date: <span t-field="lead.date_partner_assign"/>
</div>
</xpath>
<xpath expr="//div[@name='address']" position="attributes">
<attribute name="t-if">
lead.street or lead.street2 or lead.zip or lead.city or lead.state_id or lead.country_id
or lead.partner_latitude or lead.partner_longitude
</attribute>
</xpath>
<xpath expr="//div[@name='address']/*[last()]" position="after">
<div name="partner_geolocation" t-if="lead.partner_latitude or lead.partner_longitude">
Geolocation: <t t-esc="lead.partner_latitude"/> latitude, <t t-esc="lead.partner_longitude"/> longitude
</div>
</xpath>
</template>
</odoo>