Files
odoo_source/addons/event_crm/tests/test_event_crm_flow.py
T
Jérémy HennecartandThibault Delavallée 76129e3b44 [ADD] event_crm: create leads from attendees
PURPOSE

Introduce an automated tool to create leads from event registrations. Events
are a powerful source of leads as they attract attention. This leads to leads
with good quality as they mark interest while gathering contact information
which allows to follow-up.

This merge aims at automating this process by automatically creating leads
based on user-defined rules.

SPECIFICATIONS

Add a new module event_crm. It allows to create leads automatically based on
event registrations. This is done based on rules defined in a new model
event_lead_rule.

Add a new menu "Lead Generation" in the configuration tab of an event. It
allows to create generation rules. Those are a set of conditions to generate
a lead with some pre-filled values as the type of lead, tags or salesperson.

SPECIFICATIONS: CREATION TYPE

There are two types of lead creation:

  * per attendee: create a lead for each registration;
  * per order: create a lead for a group of registrations;

The last one is only available through interface if it is possible to register
a group of attendees in one action (when event_sale or website_event are
installed). Behavior itself is implemented directly in event_crm.

Basically a group is either a list of registrations belonging to the same
event and created in batch (website_event flow). With event_sale this
definition will be improved to be based on sale_order.

SPECIFICATIONS: CREATION TRIGGERS

There are three options to trigger lead creation. We consider basically that
lead quality increases if attendees confirmed or went to the event. Triggers
allow therefore to run rules:

  * at attendee creation;
  * at attendee confirmation;
  * at attendee venue;

This trigger defines when the rule will run.

SPECIFICATIONS: FILTERING REGISTRATIONS

When a batch of registrations matches the rule trigger we filter them based
on conditions and rules defines on event_lead_rule model. Heuristic is the
following:

  * the rule is active;
  * if a filter is set: filter registrations based on this filter. This is
    done like a search, and filter is a domain;
  * if a company is set on the rule, it must match event's company. Note
    that multi-company rules apply on event_lead_rule;
  * if an event category is set, it must match;
  * if an event is set, it must match;
  * if both event and category are set, one of them must match (OR). If none
    of those are set, it is considered as passing;

If conditions are met, leads are created with pre-filled informations defined
on the rule (type, user_id, team_id). Contact information coming from the
registrations are computed (customer, name, email, phone, mobile, contact_name).

SPECIFICATIONS: OTHER POINTS

Note that all rules matching their conditions are applied. This means more
than one lead can be created depending on the configuration. This is
intended in order to give more freedom to the user using the automatic
lead generation.

Once a lead is created for an event registration, a stat button on the event
is available to show the number of leads generated for this event and to
be able to find them in one click.

Additionally on the lead form, a stat button will be display to show the
registrations linked to this lead. On registrations a stat button is also
displayed to find the leads created from it or from its group.

Most of code has been written to run in batch, in case multiples rules have
to run on a set of new registrations. Tests are added to ensure behavior.

LINKS

Task ID 2166679
PR #52334
Upgrade PR odoo/upgrade#1292

Co-Authored-By: Jérémy Hennecart <jeh@odoo.com>
Co-Authored-By: Thibault Delavallée <tde@odoo.com>
2020-06-10 16:10:28 +00:00

148 lines
6.9 KiB
Python

# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.addons.event_crm.tests.common import TestEventCrmCommon
from odoo.tests.common import users
from odoo.tools import mute_logger
class TestEventCrmFlow(TestEventCrmCommon):
@classmethod
def setUpClass(cls):
super(TestEventCrmFlow, cls).setUpClass()
cls.registration_values = [
dict(customer_data, event_id=cls.event_0.id)
for customer_data in cls.batch_customer_data
]
cls.registration_values[-1]['email'] = '"John Doe" <invalid@not.example.com>'
def assert_initial_data(self):
self.assertEqual(len(self.registration_values), 5)
self.assertEqual(self.event_customer.email_normalized, 'constantin@test.example.com')
self.assertEqual(self.event_customer.phone, '0485112233')
self.assertFalse(self.event_customer.mobile)
@users('user_eventmanager')
def test_event_crm_flow_batch_create(self):
""" Test attendee- and order-based registrations creation. Event-based
creation mimics a simplified website_event flow where grouping is done
at create. """
new_registrations = self.env['event.registration'].create(self.registration_values)
self.assertEqual(len(self.event_0.registration_ids), 5)
# per-attendee rule: one lead for each registration
self.assertEqual(len(self.test_rule_attendee.lead_ids), 4)
for registration in new_registrations:
lead = self.test_rule_attendee.lead_ids.filtered(lambda lead: registration in lead.registration_ids)
# test filtering out based on domain
if registration.email == '"John Doe" <invalid@not.example.com>':
self.assertEqual(lead, self.env['crm.lead'])
continue
# only partner with matching email / phone is kept in mono attendee mode to avoid
# loosing registration-specific email / phone informations due to lead synchronization
expected_partner = registration.partner_id if registration.partner_id == self.event_customer else None
self.assertTrue(bool(lead))
self.assertLeadConvertion(self.test_rule_attendee, registration, partner=expected_partner)
# per-order rule: one lead for all registrations (same event -> same batch, website_event style)
self.assertEqual(len(self.test_rule_order.lead_ids), 1)
lead = self.test_rule_order.lead_ids
self.assertLeadConvertion(
self.test_rule_order,
new_registrations.filtered(lambda reg: reg.email != '"John Doe" <invalid@not.example.com>'),
partner=new_registrations[0].partner_id
)
# ensuring filtering out worked also at description level
self.assertNotIn('invalid@not.example.com', lead.description)
@users('user_eventmanager')
def test_event_crm_flow_batch_update(self):
""" Test update of contact or description fields that leads to lead
update. """
# initial data: create registrations in batch
new_registrations = self.env['event.registration'].create(self.registration_values)
self.assertEqual(len(self.event_0.registration_ids), 5)
self.assertEqual(len(self.test_rule_attendee.lead_ids), 4)
self.assertEqual(len(self.test_rule_order.lead_ids), 1)
# customer is updated (like a SO setting its customer)
new_registrations.write({'partner_id': self.event_customer2.id})
# per-attendee rule
self.assertEqual(len(self.test_rule_attendee.lead_ids), 4)
for registration in new_registrations:
lead = self.test_rule_attendee.lead_ids.filtered(lambda lead: registration in lead.registration_ids)
# test filtering out based on domain
if registration.email == '"John Doe" <invalid@not.example.com>':
self.assertEqual(lead, self.env['crm.lead'])
continue
# only partner with matching email / phone is kept in mono attendee mode to avoid
# loosing registration-specific email / phone informations due to lead synchronization
self.assertLeadConvertion(self.test_rule_attendee, registration, partner=None)
# per-order rule
self.assertEqual(len(self.test_rule_order.lead_ids), 1)
self.assertEqual(self.test_rule_order.lead_ids.event_id, self.event_0)
lead = self.test_rule_order.lead_ids
self.assertLeadConvertion(
self.test_rule_order,
new_registrations.filtered(lambda reg: reg.email != '"John Doe" <invalid@not.example.com>'),
partner=new_registrations[0].partner_id
)
# ensuring filtering out worked also at description level
self.assertNotIn('invalid@not.example.com', lead.description)
@users('user_eventmanager')
def test_event_crm_flow_per_attendee_single(self):
self.assert_initial_data()
# test: partner-based contact information, everything synchonized
registration = self.env['event.registration'].create({
'partner_id': self.event_customer.id,
'event_id': self.event_0.id,
})
self.assertEqual(registration.email, self.event_customer.email)
self.assertEqual(registration.phone, self.event_customer.phone)
# per-attendee rule
self.assertLeadConvertion(self.test_rule_attendee, registration, partner=registration.partner_id)
# test: no partner and contact information
registration = self.env['event.registration'].create({
'name': 'My Registration',
'partner_id': False,
'email': 'super.email@test.example.com',
'phone': False,
'mobile': '0456332211',
'event_id': self.event_0.id,
})
self.assertEqual(len(self.event_0.registration_ids), 2)
self.assertLeadConvertion(self.test_rule_attendee, registration, partner=None)
# test: no partner and few information
registration = self.env['event.registration'].create({
'name': False,
'partner_id': False,
'email': 'giga.email@test.example.com',
'phone': False,
'mobile': False,
'event_id': self.event_0.id,
})
self.assertEqual(len(self.event_0.registration_ids), 3)
self.assertLeadConvertion(self.test_rule_attendee, registration, partner=None)
# test: partner but with other contact information -> registration prior
registration = self.env['event.registration'].create({
'partner_id': self.event_customer.id,
'email': 'other.email@test.example.com',
'phone': False,
'mobile': '0456112233',
'event_id': self.event_0.id,
})
self.assertEqual(len(self.event_0.registration_ids), 4)
self.assertLeadConvertion(self.test_rule_attendee, registration, partner=None)