diff options
| author | qmo-odoo <qmo@odoo.com> | 2019-08-02 07:56:35 +0000 |
|---|---|---|
| committer | Thibault Delavallée <tde@odoo.com> | 2019-08-02 12:32:35 +0000 |
| commit | a661b00015557281faca7c88eae4aa5321a2b843 (patch) | |
| tree | 251c2fb9e07e8341ad551f2efe88acb4a05496e5 | |
| parent | 2d086f5d8fb01f659ede9543dc676e60125477ee (diff) | |
[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
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 |
