diff options
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 |
