summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--addons/crm/views/crm_lead_views.xml1
-rw-r--r--addons/link_tracker/__manifest__.py1
-rw-r--r--addons/link_tracker/models/__init__.py1
-rw-r--r--addons/link_tracker/models/link_tracker.py1
-rw-r--r--addons/link_tracker/models/utm.py22
-rw-r--r--addons/link_tracker/views/link_tracker_views.xml19
-rw-r--r--addons/link_tracker/views/utm_campaign_views.xml30
-rw-r--r--addons/mass_mailing/__manifest__.py1
-rw-r--r--addons/mass_mailing/data/mass_mailing_data.xml15
-rw-r--r--addons/mass_mailing/data/mass_mailing_demo.xml16
-rw-r--r--addons/mass_mailing/models/__init__.py1
-rw-r--r--addons/mass_mailing/models/link_tracker.py6
-rw-r--r--addons/mass_mailing/models/mailing.py204
-rw-r--r--addons/mass_mailing/models/mailing_trace.py4
-rw-r--r--addons/mass_mailing/models/utm.py101
-rw-r--r--addons/mass_mailing/report/mailing_trace_report.py3
-rw-r--r--addons/mass_mailing/security/ir.model.access.csv9
-rw-r--r--addons/mass_mailing/static/src/js/mass_mailing.js22
-rw-r--r--addons/mass_mailing/static/src/js/mass_mailing_campaign_kanban_record.js18
-rw-r--r--addons/mass_mailing/static/src/js/mass_mailing_campaign_kanban_renderer.js16
-rw-r--r--addons/mass_mailing/static/src/js/mass_mailing_campaign_kanban_view.js19
-rw-r--r--addons/mass_mailing/static/src/scss/mass_mailing.scss19
-rw-r--r--addons/mass_mailing/static/src/scss/mass_mailing_mobile.scss7
-rw-r--r--addons/mass_mailing/tests/test_mass_mailing_shortener.py2
-rw-r--r--addons/mass_mailing/views/assets.xml4
-rw-r--r--addons/mass_mailing/views/link_tracker_views.xml15
-rw-r--r--addons/mass_mailing/views/mailing_mailing_views.xml293
-rw-r--r--addons/mass_mailing/views/mailing_trace_views.xml2
-rw-r--r--addons/mass_mailing/views/res_config_settings_views.xml2
-rw-r--r--addons/mass_mailing/views/utm_campaign_views.xml126
-rw-r--r--addons/mass_mailing/wizard/mail_compose_message.py4
-rw-r--r--addons/mass_mailing/wizard/mail_compose_message_views.xml2
-rw-r--r--addons/mass_mailing_crm/data/mass_mailing_demo.xml3
-rw-r--r--addons/mass_mailing_sale/data/mass_mailing_demo.xml3
-rw-r--r--addons/test_mass_mailing/tests/test_performance.py4
-rw-r--r--addons/utm/__manifest__.py1
-rw-r--r--addons/utm/data/utm_data.xml32
-rw-r--r--addons/utm/models/utm.py45
-rw-r--r--addons/utm/models/utm_mixin.py2
-rw-r--r--addons/utm/security/ir.model.access.csv5
-rw-r--r--addons/utm/views/utm_campaign_views.xml160
-rw-r--r--addons/utm/views/utm_views.xml26
42 files changed, 646 insertions, 621 deletions
diff --git a/addons/crm/views/crm_lead_views.xml b/addons/crm/views/crm_lead_views.xml
index 2cfeb26fd0f..a10ba85000f 100644
--- a/addons/crm/views/crm_lead_views.xml
+++ b/addons/crm/views/crm_lead_views.xml
@@ -781,6 +781,7 @@
<field name="probability"/>
<field name="lost_reason"/>
<field name="date_conversion"/>
+ <field name="campaign_id"/>
<separator/>
<filter string="My Pipeline" name="assigned_to_me"
domain="[('user_id', '=', uid)]"
diff --git a/addons/link_tracker/__manifest__.py b/addons/link_tracker/__manifest__.py
index c458d23f539..3f622632cc4 100644
--- a/addons/link_tracker/__manifest__.py
+++ b/addons/link_tracker/__manifest__.py
@@ -11,6 +11,7 @@ Shorten URLs and use them to track clicks and UTMs
'depends': ['utm'],
'data': [
'views/link_tracker_views.xml',
+ 'views/utm_campaign_views.xml',
'security/ir.model.access.csv',
],
}
diff --git a/addons/link_tracker/models/__init__.py b/addons/link_tracker/models/__init__.py
index d0881ab6f62..847f935569e 100644
--- a/addons/link_tracker/models/__init__.py
+++ b/addons/link_tracker/models/__init__.py
@@ -2,3 +2,4 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import link_tracker
+from . import utm
diff --git a/addons/link_tracker/models/link_tracker.py b/addons/link_tracker/models/link_tracker.py
index 4adb4ce5b3a..ed12d1e0f01 100644
--- a/addons/link_tracker/models/link_tracker.py
+++ b/addons/link_tracker/models/link_tracker.py
@@ -231,6 +231,7 @@ class LinkTrackerClick(models.Model):
_rec_name = "link_id"
_description = "Link Tracker Click"
+ campaign_id = fields.Many2one(string='UTM Campaign', comodel_name="utm.campaign", related="link_id.campaign_id", store=True)
link_id = fields.Many2one('link.tracker', 'Link', required=True, ondelete='cascade')
ip = fields.Char(string='Internet Protocol')
country_id = fields.Many2one('res.country', 'Country')
diff --git a/addons/link_tracker/models/utm.py b/addons/link_tracker/models/utm.py
new file mode 100644
index 00000000000..537993988c0
--- /dev/null
+++ b/addons/link_tracker/models/utm.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import fields, models, api, SUPERUSER_ID
+
+
+class UtmCampaign(models.Model):
+ _inherit = ['utm.campaign']
+ _description = 'UTM Campaign'
+
+ items_total = fields.Integer(string="Number of items that are part of the campaign", compute="_compute_items_total")
+ clicked_total = fields.Integer(string="Number of items that are part of the campaign who generated a click", compute="_compute_items_total")
+ clicks_ratio = fields.Integer(string="Global Clicks Ratio for the campaign", compute="_compute_clicks_ratio")
+
+ def _compute_items_total(self):
+ """Empty method that will be implemented by inheriting classes"""
+ return
+
+ @api.depends('items_total', 'clicked_total')
+ def _compute_clicks_ratio(self):
+ for campaign in self:
+ campaign.clicks_ratio = campaign.clicked_total / campaign.items_total * 100 if campaign.items_total > 0 else 0
diff --git a/addons/link_tracker/views/link_tracker_views.xml b/addons/link_tracker/views/link_tracker_views.xml
index f5b27deff28..c6d54738f8a 100644
--- a/addons/link_tracker/views/link_tracker_views.xml
+++ b/addons/link_tracker/views/link_tracker_views.xml
@@ -9,7 +9,13 @@
<search string="Links">
<field name="url" string="Title and URL" filter_domain="['|', ('title', 'ilike', self), ('url', 'ilike', self)]"/>
<field name="title"/>
+ <field name="campaign_id"/>
+ <field name="medium_id"/>
+ <field name="source_id"/>
<group expand="0" string="Group By">
+ <filter string="Campaign" name="groupby_campaign_id" context="{'group_by': 'campaign_id'}"/>
+ <filter string="Medium" name="groupby_medium_id" context="{'group_by': 'medium_id'}"/>
+ <filter string="Source" name="groupby_source_id" context="{'group_by': 'source_id'}"/>
</group>
</search>
</field>
@@ -147,6 +153,19 @@
<field name="context">{'search_default_groupby_country_id': 1}</field>
</record>
+ <record id="link_tracker_action_campaign" model="ir.actions.act_window">
+ <field name="name">Statistics of Clicks</field>
+ <field name="res_model">link.tracker</field>
+ <field name="view_mode">tree,form,graph</field>
+ <field name="view_id" ref="link_tracker.link_tracker_view_tree"/>
+ <field name="help" type="html">
+ <p class="o_view_nocontent_smiling_face">
+ Create a new link tracker
+ </p>
+ </field>
+ <field name="context">{'search_default_campaign_id': active_id}</field>
+ </record>
+
<!-- MENUS -->
<menuitem id="link_tracker_menu_main"
name="Link Tracker"
diff --git a/addons/link_tracker/views/utm_campaign_views.xml b/addons/link_tracker/views/utm_campaign_views.xml
new file mode 100644
index 00000000000..2ff5c1c0398
--- /dev/null
+++ b/addons/link_tracker/views/utm_campaign_views.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+ <record id="utm_campaign_view_form" model="ir.ui.view">
+ <field name="name">utm.campaign.view.form</field>
+ <field name="model">utm.campaign</field>
+ <field name="inherit_id" ref="utm.utm_campaign_view_form"/>
+ <field name="arch" type="xml">
+ <xpath expr="//div[hasclass('oe_button_box')]" position="inside">
+ <button name="%(link_tracker_action_campaign)d"
+ type="action" class="oe_stat_button order-12">
+ <field name="clicks_ratio" widget="percentpie" string="Clicks"/>
+ </button>
+ </xpath>
+ </field>
+ </record>
+
+ <record id="utm_campaign_view_kanban" model="ir.ui.view">
+ <field name="name">utm.campaign.view.form</field>
+ <field name="model">utm.campaign</field>
+ <field name="inherit_id" ref="utm.utm_campaign_view_kanban"/>
+ <field name="arch" type="xml">
+ <xpath expr="//div[@id='utm_statistics']" position="inside">
+ <div class="d-flex justify-content-between align-items-center w-100 order-1 mb-1">
+ <strong>Clicks</strong> <span><field name="clicks_ratio"/> %</span>
+ </div>
+ </xpath>
+ </field>
+ </record>
+</odoo>
+
diff --git a/addons/mass_mailing/__manifest__.py b/addons/mass_mailing/__manifest__.py
index ab2077b97fb..cc4a7c0d0c7 100644
--- a/addons/mass_mailing/__manifest__.py
+++ b/addons/mass_mailing/__manifest__.py
@@ -34,6 +34,7 @@
'views/mailing_list_views.xml',
'views/mailing_mailing_views.xml',
'views/res_config_settings_views.xml',
+ 'views/utm_campaign_views.xml',
'report/mailing_trace_report_views.xml',
'views/assets.xml',
'views/mass_mailing_templates_portal.xml',
diff --git a/addons/mass_mailing/data/mass_mailing_data.xml b/addons/mass_mailing/data/mass_mailing_data.xml
index 37d21928188..1d4bdc2ae3d 100644
--- a/addons/mass_mailing/data/mass_mailing_data.xml
+++ b/addons/mass_mailing/data/mass_mailing_data.xml
@@ -1,21 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
-
- <!-- Default stages of mass mailing campaigns -->
- <record id="campaign_stage_1" model="mailing.stage">
- <field name="name">Schedule</field>
- <field name="sequence">10</field>
- </record>
- <record id="campaign_stage_2" model="mailing.stage">
- <field name="name">Design</field>
- <field name="sequence">20</field>
- </record>
- <record id="campaign_stage_3" model="mailing.stage">
- <field name="name">Sent</field>
- <field name="sequence">30</field>
- </record>
-
<!-- Cron that process the mass mailing queue -->
<record id="ir_cron_mass_mailing_queue" model="ir.cron">
<field name="name">Mass Mailing: Process queue</field>
diff --git a/addons/mass_mailing/data/mass_mailing_demo.xml b/addons/mass_mailing/data/mass_mailing_demo.xml
index af6f9ba57a4..40d04fcd0c1 100644
--- a/addons/mass_mailing/data/mass_mailing_demo.xml
+++ b/addons/mass_mailing/data/mass_mailing_demo.xml
@@ -51,21 +51,14 @@
</record>
<!-- Create campaign and mailings -->
- <record id="mass_mail_tag_1" model="mailing.tag">
- <field name="name">Marketing</field>
- <field name="color" eval="1"/>
- </record>
<record id="utm_source_0" model="utm.source">
<field name="name">Newsletter 1</field>
</record>
- <record id="mass_mail_campaign_1" model="mail.mass_mailing.campaign">
+ <record id="mass_mail_campaign_1" model="utm.campaign">
<field name="name">Newsletter</field>
- <field name="stage_id" ref="mass_mailing.campaign_stage_1"/>
+ <field name="stage_id" ref="utm.campaign_stage_1"/>
<field name="user_id" eval="ref('base.user_admin')"/>
- <field name="campaign_id" eval="ref('utm.utm_campaign_email_campaign_products')"/>
- <field name="medium_id" eval="ref('utm.utm_medium_email')"/>
- <field name="source_id" eval="ref('mass_mailing.utm_source_0')"/>
- <field name="tag_ids" eval="[(6,0,[ref('mass_mailing.mass_mail_tag_1')])]"/>
+ <field name="tag_ids" eval="[(6,0,[ref('utm.utm_tag_1')])]"/>
</record>
<record id="mass_mail_1" model="mailing.mailing">
@@ -75,8 +68,7 @@
<field name="user_id" eval="ref('base.user_admin')"/>
<field name="email_from">info@yourcompany.example.com</field>
<field name="sent_date" eval="(DateTime.today() - relativedelta(days=5)).strftime('%Y-%m-%d %H:%M:%S')"/>
- <field name="mass_mailing_campaign_id" eval="ref('mass_mail_campaign_1')"/>
- <field name="campaign_id" ref="utm.utm_campaign_email_campaign_products"/>
+ <field name="campaign_id" eval="ref('mass_mail_campaign_1')"/>
<field name="source_id" ref="mass_mailing.utm_source_0"/>
<field name="mailing_model_id" ref="base.model_res_partner"/>
<field name="mailing_domain" eval="[('parent_id', '=', ref('base.res_partner_4'))]"/>
diff --git a/addons/mass_mailing/models/__init__.py b/addons/mass_mailing/models/__init__.py
index 1e852942767..9797d43aeff 100644
--- a/addons/mass_mailing/models/__init__.py
+++ b/addons/mass_mailing/models/__init__.py
@@ -10,3 +10,4 @@ from . import mail_mail
from . import mail_template
from . import mail_thread
from . import res_config_settings
+from . import utm
diff --git a/addons/mass_mailing/models/link_tracker.py b/addons/mass_mailing/models/link_tracker.py
index ce3897e006a..5fa99fba774 100644
--- a/addons/mass_mailing/models/link_tracker.py
+++ b/addons/mass_mailing/models/link_tracker.py
@@ -8,7 +8,6 @@ class LinkTracker(models.Model):
_inherit = "link.tracker"
mass_mailing_id = fields.Many2one('mailing.mailing', string='Mass Mailing')
- mass_mailing_campaign_id = fields.Many2one('mail.mass_mailing.campaign', string='Mass Mailing Campaign')
class LinkTrackerClick(models.Model):
@@ -16,7 +15,6 @@ class LinkTrackerClick(models.Model):
mailing_trace_id = fields.Many2one('mailing.trace', string='Mail Statistics')
mass_mailing_id = fields.Many2one('mailing.mailing', string='Mass Mailing')
- mass_mailing_campaign_id = fields.Many2one('mail.mass_mailing.campaign', string='Mass Mailing Campaign')
def _prepare_click_values_from_route(self, **route_values):
click_values = super(LinkTrackerClick, self)._prepare_click_values_from_route(**route_values)
@@ -26,8 +24,8 @@ class LinkTrackerClick(models.Model):
if not trace_sudo:
click_values['mailing_trace_id'] = False
else:
- if not click_values.get('mass_mailing_campaign_id'):
- click_values['mass_mailing_campaign_id'] = trace_sudo.mass_mailing_campaign_id.id
+ if not click_values.get('campaign_id'):
+ click_values['campaign_id'] = trace_sudo.campaign_id.id
if not click_values.get('mass_mailing_id'):
click_values['mass_mailing_id'] = trace_sudo.mass_mailing_id.id
diff --git a/addons/mass_mailing/models/mailing.py b/addons/mass_mailing/models/mailing.py
index 417e7f3b6cc..2011ac6c916 100644
--- a/addons/mass_mailing/models/mailing.py
+++ b/addons/mass_mailing/models/mailing.py
@@ -32,155 +32,6 @@ MASS_MAILING_BUSINESS_MODELS = [
# Used to find inline images
image_re = re.compile(r"data:(image/[A-Za-z]+);base64,(.*)")
-
-class MailingTag(models.Model):
- """Model of categories of mass mailing, i.e. marketing, newsletter, ... """
- _name = 'mailing.tag'
- _description = 'Mass Mailing Tag'
- _order = 'name'
-
- name = fields.Char(required=True, translate=True)
- color = fields.Integer(string='Color Index')
-
- _sql_constraints = [
- ('name_uniq', 'unique (name)', "Tag name already exists !"),
- ]
-
-
-class MailingStage(models.Model):
-
- """Stage for mass mailing campaigns. """
- _name = 'mailing.stage'
- _description = 'Mass Mailing Campaign Stage'
- _order = 'sequence'
-
- name = fields.Char(required=True, translate=True)
- sequence = fields.Integer()
-
-
-class MassMailingCampaign(models.Model):
- """Model of mass mailing campaigns. """
- _name = "mail.mass_mailing.campaign"
- _description = 'Mass Mailing Campaign'
- _rec_name = "campaign_id"
- _inherits = {'utm.campaign': 'campaign_id'}
-
- stage_id = fields.Many2one('mailing.stage', string='Stage', ondelete='restrict', required=True,
- default=lambda self: self.env['mailing.stage'].search([], limit=1),
- group_expand='_group_expand_stage_ids')
- user_id = fields.Many2one(
- 'res.users', string='Responsible',
- required=True, default=lambda self: self.env.uid)
- campaign_id = fields.Many2one('utm.campaign', 'campaign_id',
- required=True, ondelete='cascade', help="This name helps you tracking your different campaign efforts, e.g. Fall_Drive, Christmas_Special")
- source_id = fields.Many2one('utm.source', string='Source',
- help="This is the link source, e.g. Search Engine, another domain,or name of email list", default=lambda self: self.env.ref('utm.utm_source_newsletter', False))
- medium_id = fields.Many2one('utm.medium', string='Medium',
- help="This is the delivery method, e.g. Postcard, Email, or Banner Ad", default=lambda self: self.env.ref('utm.utm_medium_email', False))
- tag_ids = fields.Many2many(
- 'mailing.tag', 'mail_mass_mailing_tag_rel',
- 'tag_id', 'campaign_id', string='Tags')
- mass_mailing_ids = fields.One2many(
- 'mailing.mailing', 'mass_mailing_campaign_id',
- string='Mass Mailings')
- unique_ab_testing = fields.Boolean(string='Allow A/B Testing', default=False,
- help='If checked, recipients will be mailed only once for the whole campaign. '
- 'This lets you send different mailings to randomly selected recipients and test '
- 'the effectiveness of the mailings, without causing duplicate messages.')
- color = fields.Integer(string='Color Index')
- clicks_ratio = fields.Integer(compute="_compute_clicks_ratio", string="Number of clicks")
- # trace statistics fields
- total = fields.Integer(compute="_compute_statistics")
- scheduled = fields.Integer(compute="_compute_statistics")
- failed = fields.Integer(compute="_compute_statistics")
- ignored = fields.Integer(compute="_compute_statistics")
- sent = fields.Integer(compute="_compute_statistics", string="Sent Emails")
- delivered = fields.Integer(compute="_compute_statistics")
- opened = fields.Integer(compute="_compute_statistics")
- replied = fields.Integer(compute="_compute_statistics")
- bounced = fields.Integer(compute="_compute_statistics")
- received_ratio = fields.Integer(compute="_compute_statistics", string='Received Ratio')
- opened_ratio = fields.Integer(compute="_compute_statistics", string='Opened Ratio')
- replied_ratio = fields.Integer(compute="_compute_statistics", string='Replied Ratio')
- bounced_ratio = fields.Integer(compute="_compute_statistics", string='Bounced Ratio')
- total_mailings = fields.Integer(compute="_compute_total_mailings", string='Mailings')
-
- def _compute_clicks_ratio(self):
- self.env.cr.execute("""
- SELECT COUNT(DISTINCT(stats.id)) AS nb_mails, COUNT(DISTINCT(clicks.mailing_trace_id)) AS nb_clicks, stats.mass_mailing_campaign_id AS id
- FROM mailing_trace AS stats
- LEFT OUTER JOIN link_tracker_click AS clicks ON clicks.mailing_trace_id = stats.id
- WHERE stats.mass_mailing_campaign_id IN %s
- GROUP BY stats.mass_mailing_campaign_id
- """, (tuple(self.ids), ))
-
- campaign_data = self.env.cr.dictfetchall()
- mapped_data = dict([(c['id'], 100 * c['nb_clicks'] / c['nb_mails']) for c in campaign_data])
- for campaign in self:
- campaign.clicks_ratio = mapped_data.get(campaign.id, 0)
-
- def _compute_statistics(self):
- """ Compute statistics of the mass mailing campaign """
- self.env.cr.execute("""
- SELECT
- c.id as campaign_id,
- COUNT(s.id) AS total,
- COUNT(CASE WHEN s.sent is not null THEN 1 ELSE null END) AS sent,
- COUNT(CASE WHEN s.scheduled is not null AND s.sent is null AND s.exception is null AND s.ignored is null THEN 1 ELSE null END) AS scheduled,
- COUNT(CASE WHEN s.scheduled is not null AND s.sent is null AND s.exception is not null THEN 1 ELSE null END) AS failed,
- COUNT(CASE WHEN s.scheduled is not null AND s.sent is null AND s.exception is null AND s.ignored is not null THEN 1 ELSE null END) AS ignored,
- COUNT(CASE WHEN s.id is not null AND s.bounced is null THEN 1 ELSE null END) AS delivered,
- COUNT(CASE WHEN s.opened is not null THEN 1 ELSE null END) AS opened,
- COUNT(CASE WHEN s.replied is not null THEN 1 ELSE null END) AS replied ,
- COUNT(CASE WHEN s.bounced is not null THEN 1 ELSE null END) AS bounced
- FROM
- mailing_trace s
- RIGHT JOIN
- mail_mass_mailing_campaign c
- ON (c.id = s.mass_mailing_campaign_id)
- WHERE
- c.id IN %s
- GROUP BY
- c.id
- """, (tuple(self.ids), ))
-
- for row in self.env.cr.dictfetchall():
- total = (row['total'] - row['ignored']) or 1
- row['delivered'] = row['sent'] - row['bounced']
- row['received_ratio'] = 100.0 * row['delivered'] / total
- row['opened_ratio'] = 100.0 * row['opened'] / total
- row['replied_ratio'] = 100.0 * row['replied'] / total
- row['bounced_ratio'] = 100.0 * row['bounced'] / total
- self.browse(row.pop('campaign_id')).update(row)
-
- def _compute_total_mailings(self):
- campaign_data = self.env['mailing.mailing'].read_group(
- [('mass_mailing_campaign_id', 'in', self.ids)],
- ['mass_mailing_campaign_id'], ['mass_mailing_campaign_id'])
- mapped_data = dict([(c['mass_mailing_campaign_id'][0], c['mass_mailing_campaign_id_count']) for c in campaign_data])
- for campaign in self:
- campaign.total_mailings = mapped_data.get(campaign.id, 0)
-
- def _get_recipients(self, model=None):
- """Return the recipients of a mailing campaign. This is based on the statistics
- build for each mailing. """
- res = dict.fromkeys(self.ids, {})
- for campaign in self:
- domain = [('mass_mailing_campaign_id', '=', campaign.id)]
- if model:
- domain += [('model', '=', model)]
- res[campaign.id] = set(self.env['mailing.trace'].search(domain).mapped('res_id'))
- return res
-
- @api.model
- def _group_expand_stage_ids(self, stages, domain, order):
- """ Read group customization in order to display all the stages in the
- kanban view, even if they are empty
- """
- stage_ids = stages._search([], order=order, access_rights_uid=SUPERUSER_ID)
- return stages.browse(stage_ids)
-
-
class MassMailing(models.Model):
""" MassMailing models a wave of emails for a mass mailign campaign.
A mass mailing is an occurence of sending emails. """
@@ -223,9 +74,7 @@ class MassMailing(models.Model):
attachment_ids = fields.Many2many('ir.attachment', 'mass_mailing_ir_attachments_rel',
'mass_mailing_id', 'attachment_id', string='Attachments')
keep_archives = fields.Boolean(string='Keep Archives')
- mass_mailing_campaign_id = fields.Many2one('mail.mass_mailing.campaign', string='Mass Mailing Campaign')
- campaign_id = fields.Many2one('utm.campaign', string='Campaign',
- help="This name helps you tracking your different campaign efforts, e.g. Fall_Drive, Christmas_Special")
+ campaign_id = fields.Many2one('utm.campaign', string='UTM Campaign')
source_id = fields.Many2one('utm.source', string='Source', required=True, ondelete='cascade',
help="This is the link source, e.g. Search Engine, another domain, or name of email list")
medium_id = fields.Many2one('utm.medium', string='Medium', help="Delivery method: Email")
@@ -252,6 +101,10 @@ class MassMailing(models.Model):
string='Mailing Lists')
contact_ab_pc = fields.Integer(string='A/B Testing percentage',
help='Percentage of the contacts that will be mailed. Recipients will be taken randomly.', default=100)
+ unique_ab_testing = fields.Boolean(string='Allow A/B Testing', default=False,
+ help='If checked, recipients will be mailed only once for the whole campaign. '
+ 'This lets you send different mailings to randomly selected recipients and test '
+ 'the effectiveness of the mailings, without causing duplicate messages.')
# statistics data
mailing_trace_ids = fields.One2many('mailing.trace', 'mass_mailing_id', string='Emails Statistics')
total = fields.Integer(compute="_compute_total")
@@ -340,11 +193,6 @@ class MassMailing(models.Model):
else:
mass_mailing.next_departure = cron_time
- @api.onchange('mass_mailing_campaign_id')
- def _onchange_mass_mailing_campaign_id(self):
- if self.mass_mailing_campaign_id:
- self.campaign_id = self.mass_mailing_campaign_id.campaign_id.id
-
@api.onchange('mailing_model_id', 'contact_list_ids')
def _onchange_model_and_list(self):
mailing_domain = []
@@ -531,14 +379,15 @@ class MassMailing(models.Model):
return opt_out
def _get_convert_links(self):
- vals = {
- 'mass_mailing_id': self.id,
- 'source_id': self.source_id.id,
- 'medium_id': self.medium_id.id,
- }
- if self.mass_mailing_campaign_id:
- vals['mass_mailing_campaign_id'] = self.mass_mailing_campaign_id.id
- vals['campaign_id'] = self.mass_mailing_campaign_id.campaign_id.id
+ self.ensure_one()
+ vals = {'mass_mailing_id': self.id}
+
+ if self.campaign_id:
+ vals['campaign_id'] = self.campaign_id.id
+ if self.source_id:
+ vals['source_id'] = self.source_id.id
+ if self.medium_id:
+ vals['medium_id'] = self.medium_id.id
return vals
def _get_seen_list(self):
@@ -576,9 +425,9 @@ class MassMailing(models.Model):
else:
raise UserError(_("Unsupported mass mailing model %s") % self.mailing_model_id.name)
- if self.mass_mailing_campaign_id.unique_ab_testing:
+ if self.unique_ab_testing:
query +="""
- AND s.mass_mailing_campaign_id = %%(mailing_campaign_id)s;
+ AND s.campaign_id = %%(mailing_campaign_id)s;
"""
else:
query +="""
@@ -586,7 +435,7 @@ class MassMailing(models.Model):
AND s.model = %%(target_model)s;
"""
query = query % {'target': target._table, 'mail_field': mail_field}
- params = {'mailing_id': self.id, 'mailing_campaign_id': self.mass_mailing_campaign_id.id, 'target_model': self.mailing_model_real}
+ params = {'mailing_id': self.id, 'mailing_campaign_id': self.campaign_id.id, 'target_model': self.mailing_model_real}
self._cr.execute(query, params)
seen_list = set(m[0] for m in self._cr.fetchall())
_logger.info(
@@ -613,8 +462,8 @@ class MassMailing(models.Model):
if self.contact_ab_pc < 100:
contact_nbr = self.env[self.mailing_model_real].search_count(domain)
topick = int(contact_nbr / 100.0 * self.contact_ab_pc)
- if self.mass_mailing_campaign_id and self.mass_mailing_campaign_id.unique_ab_testing:
- already_mailed = self.mass_mailing_campaign_id._get_recipients()[self.mass_mailing_campaign_id.id]
+ if self.campaign_id and self.unique_ab_testing:
+ already_mailed = self.campaign_id._get_mailing_recipients()[self.campaign_id.id]
else:
already_mailed = set([])
remaining = set(res_ids).difference(already_mailed)
@@ -671,19 +520,16 @@ class MassMailing(models.Model):
def convert_links(self):
res = {}
for mass_mailing in self:
- utm_mixin = mass_mailing.mass_mailing_campaign_id if mass_mailing.mass_mailing_campaign_id else mass_mailing
html = mass_mailing.body_html if mass_mailing.body_html else ''
vals = {'mass_mailing_id': mass_mailing.id}
- if mass_mailing.mass_mailing_campaign_id:
- vals['mass_mailing_campaign_id'] = mass_mailing.mass_mailing_campaign_id.id
- if utm_mixin.campaign_id:
- vals['campaign_id'] = utm_mixin.campaign_id.id
- if utm_mixin.source_id:
- vals['source_id'] = utm_mixin.source_id.id
- if utm_mixin.medium_id:
- vals['medium_id'] = utm_mixin.medium_id.id
+ if mass_mailing.campaign_id:
+ vals['campaign_id'] = mass_mailing.campaign_id.id
+ if mass_mailing.source_id:
+ vals['source_id'] = mass_mailing.source_id.id
+ if mass_mailing.medium_id:
+ vals['medium_id'] = mass_mailing.medium_id.id
res[mass_mailing.id] = self.env['link.tracker'].convert_links(html, vals, blacklist=['/unsubscribe_from_list'])
diff --git a/addons/mass_mailing/models/mailing_trace.py b/addons/mass_mailing/models/mailing_trace.py
index 7f8ce107d14..6245d0093ff 100644
--- a/addons/mass_mailing/models/mailing_trace.py
+++ b/addons/mass_mailing/models/mailing_trace.py
@@ -30,8 +30,8 @@ class MailingTrace(models.Model):
res_id = fields.Integer(string='Document ID')
# campaign / wave data
mass_mailing_id = fields.Many2one('mailing.mailing', string='Mass Mailing', index=True)
- mass_mailing_campaign_id = fields.Many2one(
- related='mass_mailing_id.mass_mailing_campaign_id',
+ campaign_id = fields.Many2one(
+ related='mass_mailing_id.campaign_id',
string='Mass Mailing Campaign',
store=True, readonly=True, index=True)
# Bounce and tracking
diff --git a/addons/mass_mailing/models/utm.py b/addons/mass_mailing/models/utm.py
new file mode 100644
index 00000000000..2dddd30b3ec
--- /dev/null
+++ b/addons/mass_mailing/models/utm.py
@@ -0,0 +1,101 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import api, fields, models
+
+class UtmCampaign(models.Model):
+ _inherit = 'utm.campaign'
+
+ mass_mailing_ids = fields.One2many(
+ 'mailing.mailing', 'campaign_id',
+ string='Mass Mailings')
+ mass_mailing_clicks_ratio = fields.Integer(default=0, compute="_compute_mass_mailing_clicks_ratio", string="Number of clicks")
+ mailing_items = fields.Integer(compute="_compute_mailing_items", string='Mailings')
+ mailing_clicked = fields.Integer(compute="_compute_mailing_items", string='Mailings Clicked')
+ # stat fields
+ total = fields.Integer(compute="_compute_statistics")
+ scheduled = fields.Integer(compute="_compute_statistics")
+ failed = fields.Integer(compute="_compute_statistics")
+ ignored = fields.Integer(compute="_compute_statistics")
+ sent = fields.Integer(compute="_compute_statistics", string="Sent Emails")
+ delivered = fields.Integer(compute="_compute_statistics")
+ opened = fields.Integer(compute="_compute_statistics")
+ replied = fields.Integer(compute="_compute_statistics")
+ bounced = fields.Integer(compute="_compute_statistics")
+ received_ratio = fields.Integer(compute="_compute_statistics", string='Received Ratio')
+ opened_ratio = fields.Integer(compute="_compute_statistics", string='Opened Ratio')
+ replied_ratio = fields.Integer(compute="_compute_statistics", string='Replied Ratio')
+ bounced_ratio = fields.Integer(compute="_compute_statistics", string='Bounced Ratio')
+
+ @api.depends('mailing_items', 'mailing_clicked')
+ def _compute_items_total(self):
+ for campaign in self:
+ campaign.items_total += campaign.mailing_items
+ campaign.clicked_total += campaign.mailing_clicked
+
+ def _compute_mailing_items(self):
+ super(UtmCampaign, self)._compute_clicks_ratio()
+ query = """SELECT trace.campaign_id AS campaign_id, COUNT(DISTINCT(trace.id)) AS items_total, COUNT(DISTINCT(click.mailing_trace_id)) AS clicked_total
+ FROM mailing_trace AS trace
+ LEFT OUTER JOIN link_tracker_click as click ON click.mailing_trace_id = trace.id
+ WHERE trace.campaign_id IN %s
+ GROUP BY trace.campaign_id """
+ params = [tuple(self.ids)]
+ self.env.cr.execute(query, params)
+ clicked_data = self.env.cr.dictfetchall()
+ mapped_data = {datum['campaign_id']: {'clicked_total': datum['clicked_total'], 'items_total': datum['items_total']} for datum in clicked_data}
+
+ for campaign in self:
+ campaign_items_values = mapped_data.get(campaign.id, {})
+ campaign.mailing_items = campaign_items_values.get('items_total', 0)
+ campaign.mailing_clicked = campaign_items_values.get('clicked_total', 0)
+
+ @api.depends('mailing_items', 'mailing_clicked')
+ def _compute_mass_mailing_clicks_ratio(self):
+ for campaign in self:
+ campaign.mass_mailing_clicks_ratio = campaign.mailing_clicked / campaign.mailing_items * 100 if campaign.mailing_items > 0 else 0
+
+ def _compute_statistics(self):
+ """ Compute statistics of the mass mailing campaign """
+ self.env.cr.execute("""
+ SELECT
+ c.id as campaign_id,
+ COUNT(s.id) AS total,
+ COUNT(CASE WHEN s.sent is not null THEN 1 ELSE null END) AS sent,
+ COUNT(CASE WHEN s.scheduled is not null AND s.sent is null AND s.exception is null AND s.ignored is null THEN 1 ELSE null END) AS scheduled,
+ COUNT(CASE WHEN s.scheduled is not null AND s.sent is null AND s.exception is not null THEN 1 ELSE null END) AS failed,
+ COUNT(CASE WHEN s.scheduled is not null AND s.sent is null AND s.exception is null AND s.ignored is not null THEN 1 ELSE null END) AS ignored,
+ COUNT(CASE WHEN s.id is not null AND s.bounced is null THEN 1 ELSE null END) AS delivered,
+ COUNT(CASE WHEN s.opened is not null THEN 1 ELSE null END) AS opened,
+ COUNT(CASE WHEN s.replied is not null THEN 1 ELSE null END) AS replied ,
+ COUNT(CASE WHEN s.bounced is not null THEN 1 ELSE null END) AS bounced
+ FROM
+ mailing_trace s
+ RIGHT JOIN
+ utm_campaign c
+ ON (c.id = s.campaign_id)
+ WHERE
+ c.id IN %s
+ GROUP BY
+ c.id
+ """, (tuple(self.ids), ))
+
+ for row in self.env.cr.dictfetchall():
+ total = (row['total'] - row['ignored']) or 1
+ row['delivered'] = row['sent'] - row['bounced']
+ row['received_ratio'] = 100.0 * row['delivered'] / total
+ row['opened_ratio'] = 100.0 * row['opened'] / total
+ row['replied_ratio'] = 100.0 * row['replied'] / total
+ row['bounced_ratio'] = 100.0 * row['bounced'] / total
+ self.browse(row.pop('campaign_id')).update(row)
+
+ def _get_mailing_recipients(self, model=None):
+ """Return the recipients of a mailing campaign. This is based on the statistics
+ build for each mailing. """
+ res = dict.fromkeys(self.ids, {})
+ for campaign in self:
+ domain = [('campaign_id', '=', campaign.id)]
+ if model:
+ domain += [('model', '=', model)]
+ res[campaign.id] = set(self.env['mailing.trace'].search(domain).mapped('res_id'))
+ return res
diff --git a/addons/mass_mailing/report/mailing_trace_report.py b/addons/mass_mailing/report/mailing_trace_report.py
index be48a863b1e..66f37c83599 100644
--- a/addons/mass_mailing/report/mailing_trace_report.py
+++ b/addons/mass_mailing/report/mailing_trace_report.py
@@ -45,8 +45,7 @@ class MailingTraceReport(models.Model):
FROM
mailing_trace as ms
left join mailing_mailing as mm ON (ms.mass_mailing_id=mm.id)
- left join mail_mass_mailing_campaign as mc ON (ms.mass_mailing_campaign_id=mc.id)
- left join utm_campaign as utm_campaign ON (mc.campaign_id = utm_campaign.id)
+ left join utm_campaign as utm_campaign ON (mm.campaign_id = utm_campaign.id)
left join utm_source as utm_source ON (mm.source_id = utm_source.id)
GROUP BY ms.scheduled, utm_source.name, utm_campaign.name, mm.state, mm.email_from
)""")
diff --git a/addons/mass_mailing/security/ir.model.access.csv b/addons/mass_mailing/security/ir.model.access.csv
index d131c63a741..63d65ed09c7 100644
--- a/addons/mass_mailing/security/ir.model.access.csv
+++ b/addons/mass_mailing/security/ir.model.access.csv
@@ -1,11 +1,10 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
-access_mailing_tag,access.mailing.tag,model_mailing_tag,mass_mailing.group_mass_mailing_campaign,1,1,1,1
+access_utm_tag_mass_mailing_campaign,utm.tag,utm.model_utm_tag,mass_mailing.group_mass_mailing_campaign,1,1,1,1
access_mailing_contact_mm_user,access.mailing.contact.mm.user,model_mailing_contact,mass_mailing.group_mass_mailing_user,1,1,1,1
access_mailing_contact_subscription_mm_user,access.mailing.contact.subscription.mm.user,model_mailing_contact_subscription,mass_mailing.group_mass_mailing_user,1,1,1,1
access_mailing_list_mm_user,access.mailing.list.mm.user,model_mailing_list,mass_mailing.group_mass_mailing_user,1,1,1,1
-access_mailing_stage,access.mailing.stage.mm.user,model_mailing_stage,mass_mailing.group_mass_mailing_user,1,1,1,1
-access_mass_mailing_campaign,mail.mass_mailing.campaign,model_mail_mass_mailing_campaign,mass_mailing.group_mass_mailing_user,1,1,1,1
-access_mass_mailing_campaign_system,mail.mass_mailing.campaign.system,model_mail_mass_mailing_campaign,base.group_system,1,1,1,1
+access_utm_stage,utm.stage,utm.model_utm_stage,mass_mailing.group_mass_mailing_user,1,1,1,1
+access_utm_campaign_mass_mailing_user,utm.campaign,utm.model_utm_campaign,mass_mailing.group_mass_mailing_user,1,1,1,1
access_mailing_mailing_mm_user,access.mailing.mailing.mm.user,model_mailing_mailing,mass_mailing.group_mass_mailing_user,1,1,1,1
access_mailing_mailing_system,access.mailing.mailing.system,model_mailing_mailing,base.group_system,1,1,1,1
access_mailing_trace_user,mailing.trace.user,model_mailing_trace,base.group_user,1,1,1,1
@@ -14,4 +13,4 @@ access_mailing_trace_report_mm_user,access.mailing.trace.report.mm.user,model_ma
access_utm_source,access_utm_source,utm.model_utm_source,mass_mailing.group_mass_mailing_user,1,1,1,0
access_ir_mail_server,access_ir_mail_server,base.model_ir_mail_server,mass_mailing.group_mass_mailing_user,1,0,0,0
access_mail_blacklist_mass_mailing_user,access.mail.blacklist.mass_mailing_user,mail.model_mail_blacklist,mass_mailing.group_mass_mailing_user,1,1,1,1
-access_link_tracker_mailing,access.link.tracker.mailing,link_tracker.model_link_tracker,mass_mailing.group_mass_mailing_user,1,1,1,1 \ No newline at end of file
+access_link_tracker_mailing,access.link.tracker.mailing,link_tracker.model_link_tracker,mass_mailing.group_mass_mailing_user,1,1,1,1
diff --git a/addons/mass_mailing/static/src/js/mass_mailing.js b/addons/mass_mailing/static/src/js/mass_mailing.js
index 5d156c3cd65..e74d97f562b 100644
--- a/addons/mass_mailing/static/src/js/mass_mailing.js
+++ b/addons/mass_mailing/static/src/js/mass_mailing.js
@@ -1,30 +1,8 @@
odoo.define('mass_mailing.mass_mailing', function (require) {
"use strict";
-var KanbanRecord = require('web.KanbanRecord');
var KanbanColumn = require('web.KanbanColumn');
-KanbanRecord.include({
- //--------------------------------------------------------------------------
- // Private
- //--------------------------------------------------------------------------
-
- /**
- * @override
- * @private
- */
- _openRecord: function () {
- if (this.modelName === 'mail.mass_mailing.campaign') {
- this.$('.oe_mailings').click();
- } else if (this.modelName === 'mailing.list' &&
- this.$('.o_mailing_list_kanban_boxes a')) {
- this.$('.o_mailing_list_kanban_boxes a').first().click();
- } else {
- this._super.apply(this, arguments);
- }
- },
-});
-
KanbanColumn.include({
init: function () {
this._super.apply(this, arguments);
diff --git a/addons/mass_mailing/static/src/js/mass_mailing_campaign_kanban_record.js b/addons/mass_mailing/static/src/js/mass_mailing_campaign_kanban_record.js
deleted file mode 100644
index 4a237ced9e3..00000000000
--- a/addons/mass_mailing/static/src/js/mass_mailing_campaign_kanban_record.js
+++ /dev/null
@@ -1,18 +0,0 @@
-odoo.define('mass_mailing.CampaignKanbanRecord', function (require) {
-"use strict";
-
-var KanbanRecord = require('web.KanbanRecord');
-
-var MassMailingCampaignKanbanRecord = KanbanRecord.extend({
- /**
- * @override
- * @private
- */
- _openRecord: function () {
- this.$('.oe_mailings').click();
- }
-});
-
-return MassMailingCampaignKanbanRecord;
-
-});
diff --git a/addons/mass_mailing/static/src/js/mass_mailing_campaign_kanban_renderer.js b/addons/mass_mailing/static/src/js/mass_mailing_campaign_kanban_renderer.js
deleted file mode 100644
index abc7dcc018a..00000000000
--- a/addons/mass_mailing/static/src/js/mass_mailing_campaign_kanban_renderer.js
+++ /dev/null
@@ -1,16 +0,0 @@
-odoo.define('mass_mailing.CampaignKanbanRenderer', function (require) {
-"use strict";
-
-var MassMailingCampaignKanbanRecord = require('mass_mailing.CampaignKanbanRecord');
-
-var KanbanRenderer = require('web.KanbanRenderer');
-
-var MassMailingCampaignKanbanRenderer = KanbanRenderer.extend({
- config: _.extend({}, KanbanRenderer.prototype.config, {
- KanbanRecord: MassMailingCampaignKanbanRecord,
- }),
-});
-
-return MassMailingCampaignKanbanRenderer;
-
-});
diff --git a/addons/mass_mailing/static/src/js/mass_mailing_campaign_kanban_view.js b/addons/mass_mailing/static/src/js/mass_mailing_campaign_kanban_view.js
deleted file mode 100644
index cf7be9ed894..00000000000
--- a/addons/mass_mailing/static/src/js/mass_mailing_campaign_kanban_view.js
+++ /dev/null
@@ -1,19 +0,0 @@
-odoo.define('mass_mailing.CampaignKanbanView', function (require) {
-"use strict";
-
-var MassMailingCampaignKanbanRenderer = require('mass_mailing.CampaignKanbanRenderer');
-
-var KanbanView = require('web.KanbanView');
-var view_registry = require('web.view_registry');
-
-var MassMailingCampaignKanbanView = KanbanView.extend({
- config: _.extend({}, KanbanView.prototype.config, {
- Renderer: MassMailingCampaignKanbanRenderer,
- }),
-});
-
-view_registry.add('mass_mailing_campaign_kanban', MassMailingCampaignKanbanView);
-
-return MassMailingCampaignKanbanView;
-
-});
diff --git a/addons/mass_mailing/static/src/scss/mass_mailing.scss b/addons/mass_mailing/static/src/scss/mass_mailing.scss
index 873e1b9dea0..8e20d437232 100644
--- a/addons/mass_mailing/static/src/scss/mass_mailing.scss
+++ b/addons/mass_mailing/static/src/scss/mass_mailing.scss
@@ -24,3 +24,22 @@
color: #005326;
font-size: 90%;
}
+
+@media only screen and (min-width: 1200px) {
+ .o_utm_campaign_mass_mailing_substats {
+ padding-right: 210px;
+ div {
+ margin-left: 50px;
+ }
+ }
+}
+
+@media only screen and (min-width: 768px) and (max-width: 1200px) {
+ .o_utm_campaign_mass_mailing_substats {
+ padding-right: 180px;
+ div {
+ margin-left: 30px;
+ }
+ }
+}
+
diff --git a/addons/mass_mailing/static/src/scss/mass_mailing_mobile.scss b/addons/mass_mailing/static/src/scss/mass_mailing_mobile.scss
new file mode 100644
index 00000000000..9df458597b2
--- /dev/null
+++ b/addons/mass_mailing/static/src/scss/mass_mailing_mobile.scss
@@ -0,0 +1,7 @@
+@include media-breakpoint-down(sm) {
+ .o_utm_campaign_mass_mailing_substats {
+ width: 80%;
+ margin: 0 auto;
+ justify-content: space-between !important;
+ }
+} \ No newline at end of file
diff --git a/addons/mass_mailing/tests/test_mass_mailing_shortener.py b/addons/mass_mailing/tests/test_mass_mailing_shortener.py
index bc55668579c..28af1d2df16 100644
--- a/addons/mass_mailing/tests/test_mass_mailing_shortener.py
+++ b/addons/mass_mailing/tests/test_mass_mailing_shortener.py
@@ -54,7 +54,7 @@ class TestMassMailingShortener(common.TransactionCase):
"mailing_model_id": self.env.ref('mass_mailing.model_mailing_list').id,
"mailing_domain": "[('list_ids', 'in', [%d])]" % mailing_list_A.id,
"contact_list_ids": [[6, False, [mailing_list_A.id]]],
- "mass_mailing_campaign_id": False,
+ "campaign_id": False,
"name": "sdf",
"subject": "test_00_test_mass_mailing_shortener",
"body_html": """
diff --git a/addons/mass_mailing/views/assets.xml b/addons/mass_mailing/views/assets.xml
index 03b1a1415bc..46a6cfcb5dc 100644
--- a/addons/mass_mailing/views/assets.xml
+++ b/addons/mass_mailing/views/assets.xml
@@ -3,15 +3,13 @@
<template id="assets_backend" name="mass_mailing assets" inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<link rel="stylesheet" type="text/scss" href="/mass_mailing/static/src/scss/mass_mailing.scss"/>
+ <link rel="stylesheet" type="text/scss" href="/mass_mailing/static/src/scss/mass_mailing_mobile.scss"/>
<link rel="stylesheet" href="/mass_mailing/static/src/css/email_template.css"/>
<script type="text/javascript" src="/mass_mailing/static/src/js/mass_mailing.js"></script>
<script type="text/javascript" src="/mass_mailing/static/src/js/mass_mailing_widget.js"></script>
</xpath>
<xpath expr="//script[last()]" position="after">
- <script type="text/javascript" src="/mass_mailing/static/src/js/mass_mailing_campaign_kanban_record.js"></script>
- <script type="text/javascript" src="/mass_mailing/static/src/js/mass_mailing_campaign_kanban_renderer.js"></script>
- <script type="text/javascript" src="/mass_mailing/static/src/js/mass_mailing_campaign_kanban_view.js"></script>
<script type="text/javascript" src="/mass_mailing/static/src/js/mass_mailing_list_kanban_record.js"></script>
<script type="text/javascript" src="/mass_mailing/static/src/js/mass_mailing_list_kanban_renderer.js"></script>
<script type="text/javascript" src="/mass_mailing/static/src/js/mass_mailing_list_kanban_view.js"></script>
diff --git a/addons/mass_mailing/views/link_tracker_views.xml b/addons/mass_mailing/views/link_tracker_views.xml
index 450d4e7bd77..db267948159 100644
--- a/addons/mass_mailing/views/link_tracker_views.xml
+++ b/addons/mass_mailing/views/link_tracker_views.xml
@@ -8,11 +8,11 @@
<field name="arch" type="xml">
<xpath expr="//field[@name='title']" position="after">
<field name="mass_mailing_id"/>
- <field name="mass_mailing_campaign_id"/>
+ <field name="campaign_id"/>
</xpath>
<xpath expr="//group" position="inside">
<filter string="Mass Mailing" name="groupby_mass_mailing_id" context="{'group_by': 'mass_mailing_id'}"/>
- <filter string="Mailing Campaign" name="groupby_mass_mailing_campaign_id" domain="[]" context="{'group_by': 'mass_mailing_campaign_id'}"/>
+ <filter string="Mailing Campaign" name="groupby_mass_mailing_campaign_id" domain="[]" context="{'group_by': 'campaign_id'}"/>
</xpath>
</field>
</record>
@@ -24,7 +24,6 @@
<field name="arch" type="xml">
<xpath expr="//field[@name='campaign_id']" position="before">
<field name="mass_mailing_id"/>
- <field name="mass_mailing_campaign_id"/>
</xpath>
</field>
</record>
@@ -62,12 +61,12 @@
<field name="inherit_id" ref="link_tracker.link_tracker_click_view_search"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='country_id']" position="after">
- <field name="mass_mailing_campaign_id"/>
+ <field name="campaign_id"/>
<field name="mass_mailing_id"/>
</xpath>
<xpath expr="//filter[@name='groupby_country_id']" position="after">
<filter string="Mass Mailing" name="groupby_mass_mailing_id" context="{'group_by': 'mass_mailing_id'}"/>
- <filter string="Mailing Campaign" name="groupby_mass_mailing_campaign_id" domain="[]" context="{'group_by': 'mass_mailing_campaign_id'}"/>
+ <filter string="Mailing Campaign" name="groupby_mass_mailing_campaign_id" domain="[]" context="{'group_by': 'campaign_id'}"/>
</xpath>
</field>
</record>
@@ -78,7 +77,7 @@
<field name="inherit_id" ref="link_tracker.link_tracker_click_view_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='country_id']" position="after">
- <field name="mass_mailing_campaign_id"/>
+ <field name="campaign_id"/>
<field name="mass_mailing_id"/>
<field name="mailing_trace_id"/>
</xpath>
@@ -91,7 +90,7 @@
<field name="inherit_id" ref="link_tracker.link_tracker_click_view_tree"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='link_id']" position="after">
- <field name="mass_mailing_campaign_id"/>
+ <field name="campaign_id"/>
<field name="mass_mailing_id"/>
</xpath>
</field>
@@ -103,7 +102,7 @@
<field name="inherit_id" ref="link_tracker.link_tracker_click_view_graph"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='country_id']" position="after">
- <field name="mass_mailing_campaign_id"/>
+ <field name="campaign_id"/>
<field name="mass_mailing_id"/>
</xpath>
</field>
diff --git a/addons/mass_mailing/views/mailing_mailing_views.xml b/addons/mass_mailing/views/mailing_mailing_views.xml
index a4e29b259fe..680e0dfd112 100644
--- a/addons/mass_mailing/views/mailing_mailing_views.xml
+++ b/addons/mass_mailing/views/mailing_mailing_views.xml
@@ -7,7 +7,7 @@
<field name="arch" type="xml">
<search string="Mailings">
<field name="name" string="Mailings"/>
- <field name="mass_mailing_campaign_id"/>
+ <field name="campaign_id"/>
<filter string="My Mailings" name="assigned_to_me"
domain="[('user_id', '=', uid)]"
help="Mailings that are assigned to me"/>
@@ -36,7 +36,7 @@
<field name="delivered"/>
<field name="opened"/>
<field name="replied"/>
- <field name="mass_mailing_campaign_id"
+ <field name="campaign_id"
groups="mass_mailing.group_mass_mailing_campaign"/>
</tree>
</field>
@@ -133,12 +133,6 @@
class="oe_stat_button">
<field name="replied_ratio" string="Replied" widget="percentpie"/>
</button>
- <button name="action_view_clicked"
- type="object"
- attrs="{'invisible': [('state', 'in', ('draft','test'))]}"
- class="oe_stat_button">
- <field name="clicks_ratio" string="Clicks" widget="percentpie"/>
- </button>
<button name="action_view_bounced"
type="object"
context="{'search_default_filter_bounced': True}"
@@ -150,8 +144,8 @@
<group>
<field name="active" invisible="1"/>
- <field name="name" required="1" string="Mailing Name"/>
<field name="subject" string="Subject"/>
+ <field name="name" required="1" string="Mailing Name"/>
<label for="mailing_model_id" string="Recipients"/>
<div>
<field name="mailing_model_id" widget="selection" required="1"/>
@@ -183,6 +177,7 @@
<group>
<group string="Mailing">
<field name="user_id"/>
+ <field name="unique_ab_testing" groups="base.group_no_one"/>
<field name="email_from"/>
<label for="reply_to"/>
<div>
@@ -202,20 +197,18 @@
<field name="sent_date" readonly="1" attrs="{'invisible': [('sent_date', '=', False)]}"/>
</group>
<group string="Marketing" groups="base.group_no_one,mass_mailing.group_mass_mailing_campaign">
- <field name="mass_mailing_campaign_id"
+ <field name="campaign_id"
string="Mailing Campaign"
groups="mass_mailing.group_mass_mailing_campaign"/>
- <field name="campaign_id"
- string="UTM Campaign"
- groups="base.group_no_one"/>
<field name="source_id"
- readonly="1"
string="Source"
+ readonly="1"
required="False"
groups="base.group_no_one"/>
<field name="medium_id"
string="Medium"
- groups="base.group_no_one"/>
+ required="True"
+ groups="base.group_no_one"/>
<label for="contact_ab_pc" groups="mass_mailing.group_mass_mailing_campaign"/>
<div groups="mass_mailing.group_mass_mailing_campaign">
<field name="contact_ab_pc" class="oe_inline"/> %
@@ -264,8 +257,8 @@
<div class="o_kanban_record_headings">
<h3 class="mt4 mb4"><field name="name"/></h3>
<div class="o_kanban_record_subtitle" attrs="{'invisible': [('sent_date', '=', False)]}">
- <h5 style="display: inline;"><field name="mass_mailing_campaign_id" groups="mass_mailing.group_mass_mailing_campaign"/></h5>
- <t t-if="record.mass_mailing_campaign_id.raw_value" groups="mass_mailing.group_mass_mailing_campaign"> - </t><field name="sent_date"/>
+ <h5 style="display: inline;"><field name="campaign_id" groups="mass_mailing.group_mass_mailing_campaign"/></h5>
+ <t t-if="record.campaign_id.raw_value" groups="mass_mailing.group_mass_mailing_campaign"> - </t><field name="sent_date"/>
</div>
</div>
</div>
@@ -348,8 +341,8 @@
<field name="view_mode">kanban,tree,form</field>
<field name="context">{
'search_default_assigned_to_me': 1,
- 'search_default_mass_mailing_campaign_id': [active_id],
- 'default_mass_mailing_campaign_id': active_id,
+ 'search_default_campaign_id': [active_id],
+ 'default_campaign_id': active_id,
'default_user_id': uid,
}
</field>
@@ -369,8 +362,8 @@
<field name="view_mode">form,kanban,tree</field>
<field name="context">{
'search_default_assigned_to_me': 1,
- 'search_default_mass_mailing_campaign_id': [active_id],
- 'default_mass_mailing_campaign_id': active_id,
+ 'search_default_campaign_id': [active_id],
+ 'default_campaign_id': active_id,
'default_user_id': uid,
}
</field>
@@ -390,8 +383,8 @@
<field name="view_mode">form,kanban,tree</field>
<field name="context">{
'search_default_assigned_to_me': 1,
- 'search_default_mass_mailing_campaign_id': [active_id],
- 'default_mass_mailing_campaign_id': active_id,
+ 'search_default_campaign_id': [active_id],
+ 'default_campaign_id': active_id,
'default_contact_ab_pc': 10,
'default_user_id': uid,
}
@@ -409,258 +402,4 @@
<menuitem name="Mailings" id="mass_mailing_menu" parent="mass_mailing_menu_root"
sequence="1" action="action_view_mass_mailings" groups="mass_mailing.group_mass_mailing_user"/>
- <!-- MAILING CAMPAIGN STAGE !-->
- <record model="ir.ui.view" id="view_mail_mass_mailing_stage_search">
- <field name="name">mailing.stage.search</field>
- <field name="model">mailing.stage</field>
- <field name="arch" type="xml">
- <search string="Mailings">
- <field name="name"/>
- </search>
- </field>
- </record>
-
- <record model="ir.ui.view" id="view_mail_mass_mailing_stage_tree">
- <field name="name">mailing.stage.tree</field>
- <field name="model">mailing.stage</field>
- <field name="priority">10</field>
- <field name="arch" type="xml">
- <tree string="Mailings" editable="top">
- <field name="sequence" widget="handle"/>
- <field name="name"/>
- </tree>
- </field>
- </record>
- <record id="action_view_mass_mailing_stages" model="ir.actions.act_window">
- <field name="name">Mailing Stages</field>
- <field name="res_model">mailing.stage</field>
- <field name="view_mode">tree,form</field>
- <field name="help" type="html">
- <p class="o_view_nocontent_smiling_face">
- Create a new mass mailing stage
- </p>
- </field>
- </record>
-
- <menuitem name="Campaign Stages" id="menu_view_mass_mailing_stages"
- parent="mass_mailing_configuration" sequence="1"
- groups="mass_mailing.group_mass_mailing_campaign"
- action="action_view_mass_mailing_stages"/>
-
- <!-- MAILING CAMPAIGN TAGS !-->
- <record id="mail_mass_mailing_tag_view_form" model="ir.ui.view">
- <field name="name">mailing.tag.form</field>
- <field name="model">mailing.tag</field>
- <field name="arch" type="xml">
- <form string="Campaign Tags">
- <sheet>
- <group>
- <field name="name"/>
- </group>
- </sheet>
- </form>
- </field>
- </record>
- <record id="mass_mailing_tag_action" model="ir.actions.act_window">
- <field name="name">Campaign Tags</field>
- <field name="res_model">mailing.tag</field>
- <field name="help" type="html">
- <p class="o_view_nocontent_smiling_face">
- Add a new tag
- </p>
- </field>
- </record>
-
- <menuitem id="mass_mailing_tag_menu" parent="mass_mailing_configuration" action="mass_mailing_tag_action" sequence="2" groups="base.group_no_one"/>
-
-
- <!-- MAILING CAMPAIGNS !-->
- <record model="ir.ui.view" id="view_mail_mass_mailing_campaign_search">
- <field name="name">mail.mass_mailing.campaign.search</field>
- <field name="model">mail.mass_mailing.campaign</field>
- <field name="arch" type="xml">
- <search string="Mailing Campaigns">
- <field name="name" string="Campaigns"/>
- <field name="tag_ids"/>
- <field name="user_id"/>
- <group expand="0" string="Group By">
- <filter string="Stage" name="group_stage_id"
- context="{'group_by': 'stage_id'}"/>
- <filter string="Responsible" name="group_user_id"
- context="{'group_by': 'user_id'}"/>
- </group>
- </search>
- </field>
- </record>
-
- <record model="ir.ui.view" id="view_mail_mass_mailing_campaign_tree">
- <field name="name">mail.mass_mailing.campaign.tree</field>
- <field name="model">mail.mass_mailing.campaign</field>
- <field name="priority">10</field>
- <field name="arch" type="xml">
- <tree string="Mailing Campaigns">
- <field name="name"/>
- <field name="user_id"/>
- <field name="stage_id"/>
- <field name="tag_ids"/>
- </tree>
- </field>
- </record>
-
- <record model="ir.ui.view" id="view_mail_mass_mailing_campaign_form">
- <field name="name">mail.mass_mailing.campaign.form</field>
- <field name="model">mail.mass_mailing.campaign</field>
- <field name="arch" type="xml">
- <form string="Mailing Campaign">
- <header style="min-height:31px;">
- <button name="%(action_create_mass_mailings_from_campaign)d" type="action" class="oe_highlight" string="Send new Mailing"/>
- <button name="%(action_create_ab_mass_mailings_from_campaign)d" type="action" class="oe_highlight" string="Send new A/B Testing Mailing"/>
- <span><field name="stage_id" widget="statusbar" options="{'clickable': '1'}"/></span>
- </header>
- <sheet>
- <field name="total" invisible="1"/>
- <div class="oe_button_box" name="button_box"
- attrs="{'invisible': [('total', '=', 0)]}">
- <button name="%(action_view_mass_mailings_from_campaign)d"
- type="action" class="oe_stat_button" icon="fa-envelope-o">
- <field name="total_mailings" widget="statinfo" string="Mailings"/>
- </button>
- <button name="%(action_view_mail_mail_statistics_mailing)d" type="action"
- icon="fa-envelope-o" class="oe_stat_button"
- attrs="{'invisible': [('sent', '=', 0)]}" >
- <field name="sent" string="Emails Sent" widget="statinfo"/>
- </button>
- <button name="%(action_view_mass_mailing_contacts)d"
- type="action" class="oe_stat_button">
- <field name="received_ratio" widget="percentpie" string="Delivered"/>
- </button>
- <button name="%(action_view_mass_mailing_contacts)d"
- type="action" class="oe_stat_button">
- <field name="opened_ratio" widget="percentpie" string="Opened"/>
- </button>
- <button name="%(action_view_mass_mailing_contacts)d"
- type="action" class="oe_stat_button">
- <field name="replied_ratio" widget="percentpie" string="Replied"/>
- </button>
- <button name="%(link_tracker_action_mass_mailing_campaign)d"
- type="action" class="oe_stat_button">
- <field name="clicks_ratio" widget="percentpie" string="Clicks"/>
- </button>
- </div>
- <group>
- <field name="name" string="Campaign Name"/>
- <field name="user_id"/>
- <field name="tag_ids" widget="many2many_tags" options="{'color_field': 'color', 'no_create_edit': True}"/>
- <field name="unique_ab_testing" groups="base.group_no_one"/>
- </group>
- <notebook>
- <page string="Tracking">
- <group>
- <field name="medium_id" />
- <field name="source_id" />
- </group>
- </page>
- <page string="Related Mailings" groups="base.group_no_one">
- <field name="mass_mailing_ids" readonly="1" string="Related Mailing(s)">
- <tree>
- <field name="name"/>
- <field name="sent_date"/>
- <field name="state"/>
- <field name="delivered"/>
- <field name="opened"/>
- <field name="replied"/>
- <field name="bounced"/>
- <button name="action_duplicate" type="object" string="Duplicate"/>
- </tree>
- </field>
- </page>
- </notebook>
- </sheet>
- </form>
- </field>
- </record>
-
- <record model="ir.ui.view" id="view_mail_mass_mailing_campaign_kanban">
- <field name="name">mail.mass_mailing.campaign.kanban</field>
- <field name="model">mail.mass_mailing.campaign</field>
- <field name="arch" type="xml">
- <kanban js_class="mass_mailing_campaign_kanban" default_group_by='stage_id'>
- <field name='total'/>
- <field name='color'/>
- <field name='user_id'/>
- <field name='mass_mailing_ids'/>
- <field name="stage_id"/>
- <templates>
- <t t-name="kanban-box">
- <div t-attf-class="oe_kanban_color_#{kanban_getcolor(record.color.raw_value)} oe_kanban_card oe_kanban_global_click oe_kanban_mass_mailing_campaign">
- <div class="o_dropdown_kanban dropdown">
-
- <a role="button" class="dropdown-toggle o-no-caret btn" data-toggle="dropdown" href="#" aria-label="Dropdown menu" title="Dropdown menu">
- <span class="fa fa-ellipsis-v"/>
- </a>
- <div class="dropdown-menu" role="menu">
- <t t-if="widget.editable">
- <a role="menuitem" type="edit" class="dropdown-item">Settings</a>
- </t>
- <t t-if="widget.deletable">
- <a role="menuitem" type="delete" class="dropdown-item">Delete</a>
- </t>
- <div role="separator" class="dropdown-divider"/>
- <div role="separator" class="dropdown-header">Colour</div>
- <ul class="oe_kanban_colorpicker" data-field="color"/>
- </div>
- </div>
- <div class="oe_kanban_content">
- <div class="o_kanban_record_top mb16">
- <div class="o_kanban_record_headings">
- <h3 class="oe_margin_bottom_8 o_kanban_record_title"><field name="name"/></h3>
- <a name="%(action_view_mass_mailings_from_campaign)d" type="action"
- class="oe_mailings o_kanban_record_subtitle">
- <t t-raw="record.mass_mailing_ids.raw_value.length"/> Mailings
- </a>
- </div>
- </div>
- <div class="o_kanban_record_body">
- <field name="tag_ids" widget="many2many_tags" options="{'color_field': 'color'}"/>
- </div>
- <div class="o_kanban_record_bottom mt16" style="height:90px">
- <div class="oe_kanban_bottom_left" style="height:90px">
- <field name="delivered" widget="gauge" options="{'max_field': 'total', 'style': 'width:120px; height: 90px;'}"/>
- <div>
- <strong>Opened</strong> <field name="opened_ratio"/> %<br />
- <strong>Replied</strong> <field name="replied_ratio"/> % <br />
- <strong>Clicks</strong> <field name="clicks_ratio"/> % <br />
- </div>
- </div>
- <div class="oe_kanban_bottom_right" style="height:90px">
- <img t-att-src="kanban_image('res.users', 'image_small', record.user_id.raw_value)" t-att-title="record.user_id.value" t-att-alt="record.user_id.value" width="30" height="30" class="oe_kanban_avatar"/>
- </div>
- </div>
- </div>
- <div class="oe_clear"></div>
- </div>
- </t>
- </templates>
- </kanban>
- </field>
- </record>
-
- <record id="action_view_mass_mailing_campaigns" model="ir.actions.act_window">
- <field name="name">Mailing Campaigns</field>
- <field name="res_model">mail.mass_mailing.campaign</field>
- <field name="view_mode">kanban,tree,form</field>
- <field name="help" type="html">
- <p class="o_view_nocontent_smiling_face">
- Create a new mailing campaign
- </p><p>
- Create a campaign to structure mailing and get analysis from email status.
- </p>
- </field>
- </record>
-
- <menuitem name="Campaigns" id="menu_email_campaigns"
- parent="mass_mailing_menu_root" sequence="5"
- action="action_view_mass_mailing_campaigns"
- groups="mass_mailing.group_mass_mailing_campaign"/>
-
</odoo>
diff --git a/addons/mass_mailing/views/mailing_trace_views.xml b/addons/mass_mailing/views/mailing_trace_views.xml
index 86907f2770f..f01737847b6 100644
--- a/addons/mass_mailing/views/mailing_trace_views.xml
+++ b/addons/mass_mailing/views/mailing_trace_views.xml
@@ -65,7 +65,7 @@
<group>
<field name="email" string="Email"/>
<field name="mass_mailing_id"/>
- <field name="mass_mailing_campaign_id" groups="mass_mailing.group_mass_mailing_campaign"/>
+ <field name="campaign_id" groups="mass_mailing.group_mass_mailing_campaign"/>
<field name="mail_mail_id_int" groups="base.group_no_one"/>
<field name="message_id" groups="base.group_no_one"/>
<field name="model" groups="base.group_no_one"/>
diff --git a/addons/mass_mailing/views/res_config_settings_views.xml b/addons/mass_mailing/views/res_config_settings_views.xml
index b53f392ad05..00781978dbc 100644
--- a/addons/mass_mailing/views/res_config_settings_views.xml
+++ b/addons/mass_mailing/views/res_config_settings_views.xml
@@ -21,7 +21,7 @@
</div>
<div class="content-group" attrs="{'invisible': [('group_mass_mailing_campaign', '=', False)]}">
<div class="mt16">
- <button name="%(mass_mailing.action_view_mass_mailing_stages)d"
+ <button name="%(utm.action_view_utm_stage)d"
icon="fa-arrow-right" type="action" string="Campaign Stages"
class="btn-link"/>
</div>
diff --git a/addons/mass_mailing/views/utm_campaign_views.xml b/addons/mass_mailing/views/utm_campaign_views.xml
new file mode 100644
index 00000000000..fa1f30859fe
--- /dev/null
+++ b/addons/mass_mailing/views/utm_campaign_views.xml
@@ -0,0 +1,126 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+ <record model="ir.ui.view" id="utm_campaign_view_form">
+ <field name="name">utm.campaign.view.form</field>
+ <field name="model">utm.campaign</field>
+ <field name="inherit_id" ref="utm.utm_campaign_view_form"/>
+ <field name="arch" type="xml">
+ <xpath expr="//sheet" position="before">
+ <header style="min-height:31px;">
+ <button name="%(action_create_mass_mailings_from_campaign)d" type="action" class="oe_highlight" string="Send new Mailing"/>
+ <button name="%(action_create_ab_mass_mailings_from_campaign)d" type="action" class="oe_highlight" string="Send new A/B Testing Mailing"/>
+ <span><field name="stage_id" widget="statusbar" options="{'clickable': '1'}"/></span>
+ </header>
+ </xpath>
+ <xpath expr="//div[hasclass('oe_button_box')]" position="inside">
+ <button name="%(action_view_mass_mailings_from_campaign)d"
+ type="action" class="oe_stat_button order-11" icon="fa-envelope-o">
+ <field name="mailing_items" widget="statinfo" string="Mailings"/>
+ </button>
+ </xpath>
+ <xpath expr="//notebook" position="inside">
+ <page string="Mailings" attrs="{}">
+ <group groups="base.group_no_one">
+ <field name="mass_mailing_ids" readonly="1" nolabel="1">
+ <tree>
+ <field name="name"/>
+ <field name="sent_date"/>
+ <field name="state"/>
+ <field name="delivered"/>
+ <field name="opened"/>
+ <field name="replied"/>
+ <field name="bounced"/>
+ <button name="action_duplicate" type="object" string="Duplicate"/>
+ </tree>
+ </field>
+ </group>
+ <div class="o_utm_campaign_mass_mailing_substats d-flex justify-content-end align-items-center">
+ <div class="d-flex justify-content-end align-items-center flex-column">
+ <label for="received_ratio" string="Delivered" class="m-0"/>
+ <div class="m-0">
+ <span class="text-right">
+ <field name="received_ratio"/>
+ <span>%</span>
+ </span>
+ </div>
+ </div>
+ <div class="d-flex justify-content-end align-items-center flex-column">
+ <label for="opened_ratio" string="Opened" class="m-0"/>
+ <div class="m-0">
+ <span class="text-right">
+ <field name="opened_ratio"/>
+ <span>%</span>
+ </span>
+ </div>
+ </div>
+ <div class="d-flex justify-content-end align-items-center flex-column">
+ <label for="replied_ratio" string="Replied" class="m-0"/>
+ <div class="m-0">
+ <span class="text-right">
+ <field name="replied_ratio"/>
+ <span>%</span>
+ </span>
+ </div>
+ </div>
+ <div class="d-flex justify-content-end align-items-center flex-column">
+ <label for="bounced_ratio" string="Bounced" class="m-0"/>
+ <div class="m-0">
+ <span class="text-right">
+ <field name="bounced_ratio"/>
+ <span>%</span>
+ </span>
+ </div>
+ </div>
+ </div>
+ </page>
+ </xpath>
+ </field>
+ </record>
+
+ <record model="ir.ui.view" id="utm_campaign_view_kanban">
+ <field name="name">utm.campaign.view.kanban</field>
+ <field name="model">utm.campaign</field>
+ <field name="inherit_id" ref="utm.utm_campaign_view_kanban"/>
+ <field name="arch" type="xml">
+ <xpath expr="//field[@name='user_id']" position="after">
+ <field name="mass_mailing_ids"/>
+ </xpath>
+ <xpath expr="//div[hasclass('o_kanban_record_headings')]">
+ <a name="%(action_view_mass_mailings_from_campaign)d" type="action"
+ class="oe_mailings o_kanban_record_subtitle d-inline-block">
+ <t t-raw="record.mass_mailing_ids.raw_value.length"/> Mailings
+ </a>
+ </xpath>
+ </field>
+ </record>
+
+ <record id="action_view_utm_campaigns" model="ir.actions.act_window">
+ <field name="name">UTM Campaigns</field>
+ <field name="res_model">utm.campaign</field>
+ <field name="view_mode">kanban,tree,form</field>
+ <field name="help" type="html">
+ <p class="o_view_nocontent_smiling_face">
+ Create a new UTM campaign
+ </p><p>
+ Create a campaign to structure mailing and get analysis from email status.
+ </p>
+ </field>
+ <field name="domain">[('is_website', '=', False)]</field>
+ </record>
+
+ <menuitem name="Campaigns" id="menu_email_campaigns"
+ parent="mass_mailing_menu_root" sequence="5"
+ action="action_view_utm_campaigns"
+ groups="mass_mailing.group_mass_mailing_campaign"/>
+
+ <menuitem name="Campaign Stages" id="menu_view_mass_mailing_stages"
+ parent="mass_mailing_configuration" sequence="1"
+ groups="mass_mailing.group_mass_mailing_campaign"
+ action="utm.action_view_utm_stage"/>
+
+ <menuitem id="mass_mailing_tag_menu"
+ parent="mass_mailing_configuration"
+ action="utm.action_view_utm_tag"
+ sequence="2"
+ groups="base.group_no_one"/>
+</odoo> \ No newline at end of file
diff --git a/addons/mass_mailing/wizard/mail_compose_message.py b/addons/mass_mailing/wizard/mail_compose_message.py
index d8be92aafc4..18b09bcd4ce 100644
--- a/addons/mass_mailing/wizard/mail_compose_message.py
+++ b/addons/mass_mailing/wizard/mail_compose_message.py
@@ -8,8 +8,8 @@ from odoo.tools import email_re
class MailComposeMessage(models.TransientModel):
_inherit = 'mail.compose.message'
- mass_mailing_campaign_id = fields.Many2one('mail.mass_mailing.campaign', string='Mass Mailing Campaign')
mass_mailing_id = fields.Many2one('mailing.mailing', string='Mass Mailing', ondelete='cascade')
+ campaign_id = fields.Many2one('utm.campaign', string='Mass Mailing Campaign')
mass_mailing_name = fields.Char(string='Mass Mailing Name')
mailing_list_ids = fields.Many2many('mailing.list', string='Mailing List')
@@ -28,7 +28,7 @@ class MailComposeMessage(models.TransientModel):
reply_to_mode = 'email' if self.no_auto_thread else 'thread'
reply_to = self.reply_to if self.no_auto_thread else False
mass_mailing = self.env['mailing.mailing'].create({
- 'mass_mailing_campaign_id': self.mass_mailing_campaign_id.id,
+ 'campaign_id': self.campaign_id.id,
'name': self.mass_mailing_name,
'template_id': self.template_id.id,
'state': 'done',
diff --git a/addons/mass_mailing/wizard/mail_compose_message_views.xml b/addons/mass_mailing/wizard/mail_compose_message_views.xml
index e6abc5e1fe1..07aec46f4a5 100644
--- a/addons/mass_mailing/wizard/mail_compose_message_views.xml
+++ b/addons/mass_mailing/wizard/mail_compose_message_views.xml
@@ -8,7 +8,7 @@
<field name="inherit_id" ref="mail.email_compose_message_wizard_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='notify']" position="after">
- <field name="mass_mailing_campaign_id" groups="mass_mailing.group_mass_mailing_campaign"
+ <field name="campaign_id" groups="mass_mailing.group_mass_mailing_campaign"
attrs="{'invisible': [('composition_mode', '!=', 'mass_mail')]}"/>
<field name="mass_mailing_name"
attrs="{'invisible': [('composition_mode', '!=', 'mass_mail')]}"/>
diff --git a/addons/mass_mailing_crm/data/mass_mailing_demo.xml b/addons/mass_mailing_crm/data/mass_mailing_demo.xml
index b124fbbc66c..8e7eac4f6d0 100644
--- a/addons/mass_mailing_crm/data/mass_mailing_demo.xml
+++ b/addons/mass_mailing_crm/data/mass_mailing_demo.xml
@@ -7,8 +7,7 @@
<field name="state">in_queue</field>
<field name="user_id" ref="base.user_admin"/>
<field name="schedule_date" eval="(DateTime.today() + relativedelta(days=5)).strftime('%Y-%m-%d %H:%M:%S')"/>
- <field name="mass_mailing_campaign_id" eval="ref('mass_mailing.mass_mail_campaign_1')"/>
- <field name="campaign_id" ref="utm.utm_campaign_email_campaign_products"/>
+ <field name="campaign_id" eval="ref('mass_mailing.mass_mail_campaign_1')"/>
<field name="source_id" ref="utm.utm_source_mailing"/>
<field name="mailing_model_id" ref="crm.model_crm_lead"/>
<field name="mailing_domain">[]</field>
diff --git a/addons/mass_mailing_sale/data/mass_mailing_demo.xml b/addons/mass_mailing_sale/data/mass_mailing_demo.xml
index 5e6daf0957b..6b2b543e790 100644
--- a/addons/mass_mailing_sale/data/mass_mailing_demo.xml
+++ b/addons/mass_mailing_sale/data/mass_mailing_demo.xml
@@ -7,8 +7,7 @@
<field name="state">in_queue</field>
<field name="user_id" ref="base.user_admin"/>
<field name="schedule_date" eval="(DateTime.today() + relativedelta(days=5)).strftime('%Y-%m-%d %H:%M:%S')"/>
- <field name="mass_mailing_campaign_id" eval="ref('mass_mailing.mass_mail_campaign_1')"/>
- <field name="campaign_id" ref="utm.utm_campaign_email_campaign_products"/>
+ <field name="campaign_id" eval="ref('mass_mailing.mass_mail_campaign_1')"/>
<field name="source_id" ref="sale.utm_source_sale_order_0"/>
<field name="mailing_model_id" ref="sale.model_sale_order"/>
<field name="mailing_domain">[]</field>
diff --git a/addons/test_mass_mailing/tests/test_performance.py b/addons/test_mass_mailing/tests/test_performance.py
index b3ec164653d..7fd2d36ab13 100644
--- a/addons/test_mass_mailing/tests/test_performance.py
+++ b/addons/test_mass_mailing/tests/test_performance.py
@@ -50,7 +50,7 @@ class TestMassMailPerformance(TestMassMailPerformanceBase):
'mailing_domain': [('id', 'in', self.mm_recs.ids)],
})
- with self.assertQueryCount(__system__=2477, marketing=3128):
+ with self.assertQueryCount(__system__=2478, marketing=3128):
mailing.action_send_mail()
self.assertEqual(mailing.sent, 50)
@@ -88,7 +88,7 @@ class TestMassMailBlPerformance(TestMassMailPerformanceBase):
'mailing_domain': [('id', 'in', self.mm_recs.ids)],
})
- with self.assertQueryCount(__system__=2864, marketing=3611):
+ with self.assertQueryCount(__system__=2865, marketing=3611):
mailing.action_send_mail()
self.assertEqual(mailing.sent, 50)
diff --git a/addons/utm/__manifest__.py b/addons/utm/__manifest__.py
index ffa06da03bf..b7f56a957a9 100644
--- a/addons/utm/__manifest__.py
+++ b/addons/utm/__manifest__.py
@@ -11,6 +11,7 @@ Enable management of UTM trackers: campaign, medium, source.
'depends': ['base'],
'data': [
'data/utm_data.xml',
+ 'views/utm_campaign_views.xml',
'views/utm_views.xml',
'security/ir.model.access.csv',
],
diff --git a/addons/utm/data/utm_data.xml b/addons/utm/data/utm_data.xml
index 137dceb7a15..b8c77be0dc7 100644
--- a/addons/utm/data/utm_data.xml
+++ b/addons/utm/data/utm_data.xml
@@ -1,17 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
+ <!-- UTM Stage -->
+ <record id="default_utm_stage" model="utm.stage">
+ <field name="name">Default</field>
+ <field name="sequence">10</field>
+ </record>
+ <record id="campaign_stage_1" model="utm.stage">
+ <field name="name">Schedule</field>
+ <field name="sequence">10</field>
+ </record>
+ <record id="campaign_stage_2" model="utm.stage">
+ <field name="name">Design</field>
+ <field name="sequence">20</field>
+ </record>
+ <record id="campaign_stage_3" model="utm.stage">
+ <field name="name">Sent</field>
+ <field name="sequence">30</field>
+ </record>
+
<!-- UTM Campain -->
<record model="utm.campaign" id="utm_campaign_fall_drive">
<field name="name">Sale</field>
+ <field name="stage_id" ref="utm.default_utm_stage"/>
+ <field name="is_website" eval="True"/>
</record>
<record model="utm.campaign" id="utm_campaign_christmas_special">
<field name="name">Christmas Special</field>
+ <field name="stage_id" ref="utm.default_utm_stage"/>
+ <field name="is_website" eval="True"/>
</record>
<record model="utm.campaign" id="utm_campaign_email_campaign_services">
<field name="name">Email Campaign - Services</field>
+ <field name="stage_id" ref="utm.default_utm_stage"/>
+ <field name="is_website" eval="True"/>
</record>
<record model="utm.campaign" id="utm_campaign_email_campaign_products">
<field name="name">Email Campaign - Products</field>
+ <field name="stage_id" ref="utm.default_utm_stage"/>
+ <field name="is_website" eval="True"/>
</record>
<!-- UTM Source -->
@@ -62,4 +88,10 @@
<record model="utm.medium" id="utm_medium_google_adwords">
<field name="name">Google Adwords</field>
</record>
+
+ <!-- UTM Tag -->
+ <record id="utm_tag_1" model="utm.tag">
+ <field name="name">Marketing</field>
+ <field name="color" eval="1"/>
+ </record>
</odoo>
diff --git a/addons/utm/models/utm.py b/addons/utm/models/utm.py
index 92046d17bea..3c197d1814e 100644
--- a/addons/utm/models/utm.py
+++ b/addons/utm/models/utm.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-from odoo import fields, models
+from odoo import fields, models, api, SUPERUSER_ID
class UtmMedium(models.Model):
@@ -21,9 +21,52 @@ class UtmCampaign(models.Model):
name = fields.Char(string='Campaign Name', required=True, translate=True)
+ user_id = fields.Many2one(
+ 'res.users', string='Responsible',
+ required=True, default=lambda self: self.env.uid)
+ stage_id = fields.Many2one('utm.stage', string='Stage', ondelete='restrict', required=True,
+ default=lambda self: self.env['utm.stage'].search([], limit=1),
+ group_expand='_group_expand_stage_ids')
+ tag_ids = fields.Many2many(
+ 'utm.tag', 'utm_tag_rel',
+ 'tag_id', 'campaign_id', string='Tags')
+
+ is_website = fields.Boolean(default=False, help="Allows us to filter relevant Campaign")
+ color = fields.Integer(string='Color Index')
+
+ @api.model
+ def _group_expand_stage_ids(self, stages, domain, order):
+ """ Read group customization in order to display all the stages in the
+ kanban view, even if they are empty
+ """
+ stage_ids = stages._search([], order=order, access_rights_uid=SUPERUSER_ID)
+ return stages.browse(stage_ids)
class UtmSource(models.Model):
_name = 'utm.source'
_description = 'UTM Source'
name = fields.Char(string='Source Name', required=True, translate=True)
+
+class UtmStage(models.Model):
+
+ """Stage for utm campaigns. """
+ _name = 'utm.stage'
+ _description = 'Campaign Stage'
+ _order = 'sequence'
+
+ name = fields.Char(required=True, translate=True)
+ sequence = fields.Integer()
+
+class UtmTag(models.Model):
+ """Model of categories of utm campaigns, i.e. marketing, newsletter, ... """
+ _name = 'utm.tag'
+ _description = 'UTM Tag'
+ _order = 'name'
+
+ name = fields.Char(required=True, translate=True)
+ color = fields.Integer(string='Color Index')
+
+ _sql_constraints = [
+ ('name_uniq', 'unique (name)', "Tag name already exists !"),
+ ]
diff --git a/addons/utm/models/utm_mixin.py b/addons/utm/models/utm_mixin.py
index e77325d32c3..1b4be171324 100644
--- a/addons/utm/models/utm_mixin.py
+++ b/addons/utm/models/utm_mixin.py
@@ -37,7 +37,7 @@ class UtmMixin(models.AbstractModel):
Model = self.env[field.comodel_name]
records = Model.search([('name', '=', value)], limit=1)
if not records:
- records = Model.create({'name': value})
+ records = Model.create({'name': value, 'is_website': True})
value = records.id
if value:
values[field_name] = value
diff --git a/addons/utm/security/ir.model.access.csv b/addons/utm/security/ir.model.access.csv
index 3e31842f9d9..3602e9c8d5b 100644
--- a/addons/utm/security/ir.model.access.csv
+++ b/addons/utm/security/ir.model.access.csv
@@ -1,7 +1,12 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_utm_campaign_user,access_utm_campaign_user,model_utm_campaign,base.group_user,1,1,1,0
+access_utm_campaign_system,utm.campaign.system,model_utm_campaign,base.group_system,1,1,1,1
access_utm_medium_user,access_utm_medium_user,model_utm_medium,base.group_user,1,1,1,0
access_utm_source_user,access_utm_source_user,model_utm_source,base.group_user,1,1,1,0
+access_utm_stage_user,mail.utm.stage,model_utm_stage,base.group_user,1,0,0,0
+access_utm_stage_system,mail.utm.stage,model_utm_stage,base.group_system,1,1,1,1
access_utm_campaign,access_utm_campaign,model_utm_campaign,,1,0,1,0
access_utm_medium,access_utm_medium,model_utm_medium,,1,0,1,0
access_utm_source,access_utm_source,model_utm_source,,1,0,1,0
+access_utm_tag_user,utm.tag,model_utm_tag,base.group_user,1,0,0,0
+access_utm_tag_system,utm.tag,model_utm_tag,base.group_system,1,1,1,1
diff --git a/addons/utm/views/utm_campaign_views.xml b/addons/utm/views/utm_campaign_views.xml
new file mode 100644
index 00000000000..e333d1592f7
--- /dev/null
+++ b/addons/utm/views/utm_campaign_views.xml
@@ -0,0 +1,160 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+ <record model="ir.ui.view" id="view_utm_campaign_view_search">
+ <field name="name">utm.campaign.view.search</field>
+ <field name="model">utm.campaign</field>
+ <field name="arch" type="xml">
+ <search string="UTM Campaigns">
+ <field name="name" string="Campaigns"/>
+ <field name="tag_ids"/>
+ <field name="user_id"/>
+ <group expand="0" string="Group By">
+ <filter string="Stage" name="group_stage_id"
+ context="{'group_by': 'stage_id'}"/>
+ <filter string="Responsible" name="group_user_id"
+ context="{'group_by': 'user_id'}"/>
+ </group>
+ </search>
+ </field>
+ </record>
+
+ <record model="ir.ui.view" id="utm_campaign_view_form">
+ <field name="name">utm.campaign.view.form</field>
+ <field name="model">utm.campaign</field>
+ <field name="arch" type="xml">
+ <form string="UTM Campaign">
+ <sheet>
+ <div class="oe_button_box d-flex justify-content-end" name="button_box">
+ </div>
+ <group id="top-group">
+ <field name="name" string="Campaign Name"/>
+ <field name="user_id"/>
+ <field name="tag_ids" widget="many2many_tags" options="{'color_field': 'color', 'no_create_edit': True}"/>
+ </group>
+ <notebook>
+ </notebook>
+ </sheet>
+ </form>
+ </field>
+ </record>
+
+ <record model="ir.ui.view" id="utm_campaign_view_tree">
+ <field name="name">utm.campaign.view.tree</field>
+ <field name="model">utm.campaign</field>
+ <field name="arch" type="xml">
+ <tree string="UTM Campaigns">
+ <field name="name"/>
+ <field name="user_id"/>
+ <field name="stage_id"/>
+ <field name="tag_ids"/>
+ </tree>
+ </field>
+ </record>
+
+ <record model="ir.ui.view" id="utm_campaign_view_kanban">
+ <field name="name">utm.campaign.view.kanban</field>
+ <field name="model">utm.campaign</field>
+ <field name="arch" type="xml">
+ <kanban default_group_by='stage_id'>
+ <field name='color'/>
+ <field name='user_id'/>
+ <field name="stage_id"/>
+ <templates>
+ <t t-name="kanban-box">
+ <div t-attf-class="oe_kanban_color_#{kanban_getcolor(record.color.raw_value)} oe_kanban_card oe_kanban_global_click">
+ <div class="o_dropdown_kanban dropdown">
+ <a role="button" class="dropdown-toggle o-no-caret btn" data-toggle="dropdown" href="#" aria-label="Dropdown menu" title="Dropdown menu">
+ <span class="fa fa-ellipsis-v"/>
+ </a>
+ <div class="dropdown-menu" role="menu">
+ <t t-if="widget.editable">
+ <a role="menuitem" type="edit" class="dropdown-item">Settings</a>
+ </t>
+ <t t-if="widget.deletable">
+ <a role="menuitem" type="delete" class="dropdown-item">Delete</a>
+ </t>
+ <div role="separator" class="dropdown-divider"/>
+ <div role="separator" class="dropdown-header">Colour</div>
+ <ul class="oe_kanban_colorpicker" data-field="color"/>
+ </div>
+ </div>
+ <div class="oe_kanban_content">
+ <div class="o_kanban_record_top mb16">
+ <div class="o_kanban_record_headings">
+ <h3 class="oe_margin_bottom_8 o_kanban_record_title"><field name="name"/></h3>
+ </div>
+ </div>
+ <div class="o_kanban_record_body">
+ <field name="tag_ids" widget="many2many_tags" options="{'color_field': 'color'}"/>
+ </div>
+ <div class="o_kanban_record_bottom mt16" style="height:90px">
+ <div id="utm_statistics" class="d-flex flex-column justify-content-start align-items-start w-100"/>
+ </div>
+ </div>
+ <div class="oe_clear"></div>
+ </div>
+ </t>
+ </templates>
+ </kanban>
+ </field>
+ </record>
+
+ <!-- CAMPAIGN TAGS !-->
+ <record id="utm_tag_view_form" model="ir.ui.view">
+ <field name="name">utm.tag.view.form</field>
+ <field name="model">utm.tag</field>
+ <field name="arch" type="xml">
+ <form string="Campaign Tags">
+ <sheet>
+ <group>
+ <field name="name"/>
+ </group>
+ </sheet>
+ </form>
+ </field>
+ </record>
+
+ <record id="action_view_utm_tag" model="ir.actions.act_window">
+ <field name="name">Campaign Tags</field>
+ <field name="res_model">utm.tag</field>
+ <field name="help" type="html">
+ <p class="o_view_nocontent_smiling_face">
+ Add a new tag
+ </p>
+ </field>
+ </record>
+
+ <!-- CAMPAIGN STAGE !-->
+ <record model="ir.ui.view" id="utm_stage_view_search">
+ <field name="name">utm.stage.view.search</field>
+ <field name="model">utm.stage</field>
+ <field name="arch" type="xml">
+ <search string="Stages">
+ <field name="name"/>
+ </search>
+ </field>
+ </record>
+
+ <record model="ir.ui.view" id="utm_stage_view_tree">
+ <field name="name">utm.stage.view.tree</field>
+ <field name="model">utm.stage</field>
+ <field name="priority">10</field>
+ <field name="arch" type="xml">
+ <tree string="Stages" editable="top">
+ <field name="sequence" widget="handle"/>
+ <field name="name"/>
+ </tree>
+ </field>
+ </record>
+
+ <record id="action_view_utm_stage" model="ir.actions.act_window">
+ <field name="name">UTM Stages</field>
+ <field name="res_model">utm.stage</field>
+ <field name="view_mode">tree,form</field>
+ <field name="help" type="html">
+ <p class="o_view_nocontent_smiling_face">
+ Create a new utm stage
+ </p>
+ </field>
+ </record>
+</odoo> \ No newline at end of file
diff --git a/addons/utm/views/utm_views.xml b/addons/utm/views/utm_views.xml
index b2f5ccbea74..8bf401e0de3 100644
--- a/addons/utm/views/utm_views.xml
+++ b/addons/utm/views/utm_views.xml
@@ -11,34 +11,10 @@
sequence="99"
groups="base.group_no_one"/>
- <!-- utm.campaign -->
- <record id="utm_campaign_view_tree" model="ir.ui.view">
- <field name="name">utm.campaign.view.tree</field>
- <field name="model">utm.campaign</field>
- <field name="arch" type="xml">
- <tree string="Campaign" editable="bottom">
- <field name="name"/>
- </tree>
- </field>
- </record>
- <record id="utm_campaign_view_form" model="ir.ui.view">
- <field name="name">utm.campaign.view.form</field>
- <field name="model">utm.campaign</field>
- <field name="arch" type="xml">
- <form string="Campaign">
- <sheet>
- <group>
- <field name="name"/>
- </group>
- </sheet>
- </form>
- </field>
- </record>
-
<record id="utm_campaign_action" model="ir.actions.act_window">
<field name="name">Campaigns</field>
<field name="res_model">utm.campaign</field>
- <field name="view_id" ref="utm_campaign_view_tree"/>
+ <field name="view_mode">kanban,tree,form</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Define a new UTM campaign