From a661b00015557281faca7c88eae4aa5321a2b843 Mon Sep 17 00:00:00 2001 From: qmo-odoo Date: Fri, 2 Aug 2019 07:56:35 +0000 Subject: [PATCH 1/6] [REF] utm,mass_mailing: replace mass mailing campaign by utm campaign PURPOSE This commit removes the mass_mailing.campaign model. Instead of having a fully fledged model, we will simply inherit utm.campaign. We will also add relevant statistics on utm campaign model in order to use it in various applications. SPECIFICATIONS This commit removes the mass_mailing.campaign model. Instead of having a fully fledged model, we will simply inherit utm.campaign. This change implies that mass_mailing.tag and mass_mailing.stage have to move to the utm model along their associated views/data. These changes were made so that campaigns could be used in the future by social, mass_mailing and mass_sms and available in the same view This commit also removes the source_id and the medium_id fields on the campaign. This commit also moves the unique_ab_testing field from the mass_mailing_campaign to the mass_mailing model Task ID: 2002029 PR: #34015 --- addons/crm/views/crm_lead_views.xml | 1 + addons/link_tracker/__manifest__.py | 1 + addons/link_tracker/models/__init__.py | 1 + addons/link_tracker/models/link_tracker.py | 1 + addons/link_tracker/models/utm.py | 22 ++ .../link_tracker/views/link_tracker_views.xml | 19 ++ .../link_tracker/views/utm_campaign_views.xml | 30 ++ addons/mass_mailing/__manifest__.py | 1 + .../mass_mailing/data/mass_mailing_data.xml | 15 - .../mass_mailing/data/mass_mailing_demo.xml | 16 +- addons/mass_mailing/models/__init__.py | 1 + addons/mass_mailing/models/link_tracker.py | 6 +- addons/mass_mailing/models/mailing.py | 204 ++---------- addons/mass_mailing/models/mailing_trace.py | 4 +- addons/mass_mailing/models/utm.py | 101 ++++++ .../report/mailing_trace_report.py | 3 +- .../mass_mailing/security/ir.model.access.csv | 9 +- .../static/src/js/mass_mailing.js | 22 -- .../js/mass_mailing_campaign_kanban_record.js | 18 -- .../mass_mailing_campaign_kanban_renderer.js | 16 - .../js/mass_mailing_campaign_kanban_view.js | 19 -- .../static/src/scss/mass_mailing.scss | 19 ++ .../static/src/scss/mass_mailing_mobile.scss | 7 + .../tests/test_mass_mailing_shortener.py | 2 +- addons/mass_mailing/views/assets.xml | 4 +- .../mass_mailing/views/link_tracker_views.xml | 15 +- .../views/mailing_mailing_views.xml | 293 +----------------- .../views/mailing_trace_views.xml | 2 +- .../views/res_config_settings_views.xml | 2 +- .../mass_mailing/views/utm_campaign_views.xml | 126 ++++++++ .../wizard/mail_compose_message.py | 4 +- .../wizard/mail_compose_message_views.xml | 2 +- .../data/mass_mailing_demo.xml | 3 +- .../data/mass_mailing_demo.xml | 3 +- .../tests/test_performance.py | 4 +- addons/utm/__manifest__.py | 1 + addons/utm/data/utm_data.xml | 32 ++ addons/utm/models/utm.py | 45 ++- addons/utm/models/utm_mixin.py | 2 +- addons/utm/security/ir.model.access.csv | 5 + addons/utm/views/utm_campaign_views.xml | 160 ++++++++++ addons/utm/views/utm_views.xml | 26 +- 42 files changed, 646 insertions(+), 621 deletions(-) create mode 100644 addons/link_tracker/models/utm.py create mode 100644 addons/link_tracker/views/utm_campaign_views.xml create mode 100644 addons/mass_mailing/models/utm.py delete mode 100644 addons/mass_mailing/static/src/js/mass_mailing_campaign_kanban_record.js delete mode 100644 addons/mass_mailing/static/src/js/mass_mailing_campaign_kanban_renderer.js delete mode 100644 addons/mass_mailing/static/src/js/mass_mailing_campaign_kanban_view.js create mode 100644 addons/mass_mailing/static/src/scss/mass_mailing_mobile.scss create mode 100644 addons/mass_mailing/views/utm_campaign_views.xml create mode 100644 addons/utm/views/utm_campaign_views.xml 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 @@ + 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 @@ + + + + + + @@ -147,6 +153,19 @@ {'search_default_groupby_country_id': 1} + + Statistics of Clicks + link.tracker + tree,form,graph + + +

+ Create a new link tracker +

+
+ {'search_default_campaign_id': active_id} +
+ + + + utm.campaign.view.form + utm.campaign + + + + + + + + + + utm.campaign.view.form + utm.campaign + + + +
+ Clicks % +
+
+
+
+
+ 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 @@ - - - - Schedule - 10 - - - Design - 20 - - - Sent - 30 - - Mass Mailing: Process queue 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 @@ - - Marketing - - Newsletter 1 - + Newsletter - + - - - - + @@ -75,8 +68,7 @@ info@yourcompany.example.com - - + 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 @@