[MERGE][REF] utm, mass_mailing: improve and use utm.campaign
This merge 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 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. Indeed those depends on the flow, not the campaign (mass mailing is a source, email is a medium for example). In this merge we also add statistics from crm and sales to the campaign model like what has been done for mass mailing previously. Task ID: 2002029& PR: #34015 closes odoo/odoo#34452 Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com>
This commit is contained in:
@@ -47,6 +47,7 @@
|
||||
'report/crm_opportunity_report_views.xml',
|
||||
'views/crm_team_views.xml',
|
||||
'views/digest_views.xml',
|
||||
'views/utm_campaign_views.xml'
|
||||
],
|
||||
'demo': [
|
||||
'data/crm_demo.xml',
|
||||
|
||||
@@ -10,3 +10,4 @@ from . import res_config_settings
|
||||
from . import res_partner
|
||||
from . import digest
|
||||
from . import crm_lead_scoring_frequency
|
||||
from . import utm
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
# -*- 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'
|
||||
|
||||
crm_lead_activated = fields.Boolean('Use Leads', compute='_compute_crm_lead_activated')
|
||||
lead_count = fields.Integer('Lead Count', groups='sales_team.group_sale_salesman', compute="_compute_global_opportunity_and_lead_count")
|
||||
opportunity_count = fields.Integer('Opportunity Count', groups='sales_team.group_sale_salesman', compute="_compute_global_opportunity_and_lead_count")
|
||||
|
||||
def _compute_crm_lead_activated(self):
|
||||
for campaign in self:
|
||||
campaign.crm_lead_activated = self.env.user.has_group('crm.group_use_lead')
|
||||
|
||||
def _compute_global_opportunity_and_lead_count(self):
|
||||
lead_data = self.env['crm.lead'].with_context(active_test=False).read_group([
|
||||
('campaign_id', 'in', self.ids)],
|
||||
['campaign_id'], ['campaign_id'])
|
||||
data_map = {datum['campaign_id'][0]: datum['campaign_id_count'] for datum in lead_data}
|
||||
if self.env.user.has_group('crm.group_use_lead'):
|
||||
for campaign in self:
|
||||
campaign.lead_count = data_map.get(campaign.id, 0)
|
||||
campaign.opportunity_count = 0
|
||||
else:
|
||||
for campaign in self:
|
||||
campaign.lead_count = 0
|
||||
campaign.opportunity_count = data_map.get(campaign.id, 0)
|
||||
|
||||
def action_redirect_to_leads(self):
|
||||
action = self.env.ref('crm.crm_lead_all_leads').read()[0]
|
||||
action['domain'] = [('campaign_id', '=', self.id)]
|
||||
action['context'] = {'default_type': 'lead', 'active_test': False}
|
||||
return action
|
||||
|
||||
def action_redirect_to_opportunities(self):
|
||||
action = self.env.ref('crm.crm_lead_opportunities').read()[0]
|
||||
action['view_mode'] = 'tree,kanban,graph,pivot,form,calendar'
|
||||
action['domain'] = [('campaign_id', '=', self.id)]
|
||||
action['context'] = {'active_test': False}
|
||||
return action
|
||||
@@ -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)]"
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<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="//div[@id='utm_statistics']" position="inside">
|
||||
<field name="crm_lead_activated" invisible="1"/>
|
||||
<div class="d-flex justify-content-between align-items-center w-100 order-3 mb-1"
|
||||
groups="sales_team.group_sale_salesman"
|
||||
attrs="{'invisible': [('crm_lead_activated', '=', False)]}">
|
||||
<strong>Leads</strong> <span><field name="lead_count"/></span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between align-items-center w-100 order-3 mb-1"
|
||||
groups="sales_team.group_sale_salesman"
|
||||
attrs="{'invisible': [('crm_lead_activated', '=', True)]}">
|
||||
<strong>Opportunities</strong> <span><field name="opportunity_count"/></span>
|
||||
</div>
|
||||
</xpath>
|
||||
</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="inherit_id" ref="utm.utm_campaign_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//div[hasclass('oe_button_box')]" position="inside">
|
||||
<field name="crm_lead_activated" invisible="1"/>
|
||||
<button name="action_redirect_to_leads"
|
||||
type="object"
|
||||
class="oe_stat_button order-3"
|
||||
icon="fa-star"
|
||||
groups="sales_team.group_sale_salesman"
|
||||
attrs="{'invisible': [('crm_lead_activated', '=', False)]}">
|
||||
<field name="lead_count" widget="statinfo" string="Leads"/>
|
||||
</button>
|
||||
<button name="action_redirect_to_opportunities"
|
||||
type="object"
|
||||
class="oe_stat_button order-3"
|
||||
icon="fa-star"
|
||||
groups="sales_team.group_sale_salesman"
|
||||
attrs="{'invisible': [('crm_lead_activated', '=', True)]}">
|
||||
<field name="opportunity_count" widget="statinfo" string="Opportunities"/>
|
||||
</button>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -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',
|
||||
],
|
||||
}
|
||||
|
||||
@@ -2,3 +2,4 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import link_tracker
|
||||
from . import utm
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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'))]"/>
|
||||
|
||||
@@ -10,3 +10,4 @@ from . import mail_mail
|
||||
from . import mail_template
|
||||
from . import mail_thread
|
||||
from . import res_config_settings
|
||||
from . import utm
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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'])
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
mailing_ids = fields.One2many(
|
||||
'mailing.mailing', 'campaign_id',
|
||||
string='Mass Mailings')
|
||||
mailing_clicks_ratio = fields.Integer(default=0, compute="_compute_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_mailing_clicks_ratio(self):
|
||||
for campaign in self:
|
||||
campaign.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
|
||||
@@ -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
|
||||
)""")
|
||||
|
||||
@@ -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
|
||||
access_link_tracker_mailing,access.link.tracker.mailing,link_tracker.model_link_tracker,mass_mailing.group_mass_mailing_user,1,1,1,1
|
||||
|
||||
|
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
});
|
||||
@@ -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;
|
||||
|
||||
});
|
||||
@@ -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;
|
||||
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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": """
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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="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="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.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>
|
||||
@@ -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',
|
||||
|
||||
@@ -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')]}"/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -36,17 +36,21 @@ class MassMailing(models.Model):
|
||||
def action_redirect_to_quotations(self):
|
||||
action = self.env.ref('sale.action_quotations_with_onboarding').read()[0]
|
||||
action['domain'] = self._get_sale_utm_domain()
|
||||
action['context'] = {'default_type': 'lead', 'create': False}
|
||||
action['context'] = {'create': False}
|
||||
return action
|
||||
|
||||
def action_redirect_to_invoiced(self):
|
||||
action = self.env.ref('account.view_move_form').read()[0]
|
||||
action = self.env.ref('account.action_move_journal_line').read()[0]
|
||||
invoices = self.env['sale.order'].search(self._get_sale_utm_domain()).mapped('invoice_ids')
|
||||
action['context'] = {
|
||||
'create': False,
|
||||
'edit': False,
|
||||
'view_no_maturity': True
|
||||
}
|
||||
action['domain'] = [
|
||||
('id', 'in', invoices.ids),
|
||||
('type', 'in', ('out_invoice', 'out_refund')),
|
||||
('type', '=', 'posted'),
|
||||
('partner_id', 'child_of', self.id),
|
||||
('type', 'in', ('out_invoice', 'out_refund', 'in_invoice', 'in_refund', 'out_receipt', 'in_receipt')),
|
||||
('state', 'not in', ['draft', 'cancel'])
|
||||
]
|
||||
action['context'] = {'create': False}
|
||||
return action
|
||||
|
||||
@@ -33,6 +33,7 @@ This module contains all the common features of Sales Management and eCommerce.
|
||||
'views/res_config_settings_views.xml',
|
||||
'views/payment_views.xml',
|
||||
'views/product_attribute_views.xml',
|
||||
'views/utm_campaign_views.xml'
|
||||
],
|
||||
'demo': [
|
||||
'data/sale_demo.xml',
|
||||
|
||||
@@ -13,3 +13,4 @@ from . import sale
|
||||
from . import res_config_settings
|
||||
from . import sales_team
|
||||
from . import payment
|
||||
from . import utm
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
# -*- 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'
|
||||
|
||||
quotation_count = fields.Integer('Quotation Count', groups='sales_team.group_sale_salesman', compute="_compute_quotation_count")
|
||||
invoiced_amount = fields.Integer(default=0, compute="_compute_sale_invoiced_amount", string="Revenues generated by the campaign")
|
||||
company_id = fields.Many2one('res.company', string='Company', readonly=True, states={'draft': [('readonly', False)], 'refused': [('readonly', False)]}, default=lambda self: self.env.company)
|
||||
currency_id = fields.Many2one('res.currency', related='company_id.currency_id', string='Currency')
|
||||
|
||||
def _compute_quotation_count(self):
|
||||
quotation_data = self.env['sale.order'].read_group([
|
||||
('campaign_id', 'in', self.ids)],
|
||||
['campaign_id'], ['campaign_id'])
|
||||
data_map = {datum['campaign_id'][0]: datum['campaign_id_count'] for datum in quotation_data}
|
||||
for campaign in self:
|
||||
campaign.quotation_count = data_map.get(campaign.id, 0)
|
||||
|
||||
def _compute_sale_invoiced_amount(self):
|
||||
query = """SELECT move.campaign_id, -SUM(line.balance) as price_subtotal
|
||||
FROM account_move_line line
|
||||
INNER JOIN account_move move ON line.move_id = move.id
|
||||
WHERE move.state not in ('draft', 'cancel')
|
||||
AND move.campaign_id IN %s
|
||||
AND move.type IN ('out_invoice', 'out_refund', 'in_invoice', 'in_refund', 'out_receipt', 'in_receipt')
|
||||
AND line.account_id IS NOT NULL
|
||||
AND NOT line.exclude_from_invoice_tab
|
||||
GROUP BY move.campaign_id
|
||||
"""
|
||||
|
||||
self._cr.execute(query, [tuple(self.ids)])
|
||||
query_res = self._cr.dictfetchall()
|
||||
|
||||
for datum in query_res:
|
||||
campaign = self.browse(datum['campaign_id'])
|
||||
campaign.invoiced_amount = datum['price_subtotal']
|
||||
|
||||
def action_redirect_to_quotations(self):
|
||||
action = self.env.ref('sale.action_quotations_with_onboarding').read()[0]
|
||||
action['domain'] = [('campaign_id', '=', self.id)]
|
||||
action['context'] = {'default_campaign_id': self.id}
|
||||
return action
|
||||
|
||||
def action_redirect_to_invoiced(self):
|
||||
action = self.env.ref('account.action_move_journal_line').read()[0]
|
||||
invoices = self.env['account.move'].search([('campaign_id', '=', self.id)])
|
||||
action['context'] = {
|
||||
'create': False,
|
||||
'edit': False,
|
||||
'view_no_maturity': True
|
||||
}
|
||||
action['domain'] = [
|
||||
('id', 'in', invoices.ids),
|
||||
('type', 'in', ('out_invoice', 'out_refund', 'in_invoice', 'in_refund', 'out_receipt', 'in_receipt')),
|
||||
('state', 'not in', ['draft', 'cancel'])
|
||||
]
|
||||
return action
|
||||
@@ -701,6 +701,7 @@
|
||||
<field name="inherit_id" ref="sale.view_sales_order_filter"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//filter[@name='my_sale_orders_filter']" position="replace">
|
||||
<field name="campaign_id"/>
|
||||
<separator/>
|
||||
<filter string="My Quotations" name="my_quotation" domain="[('user_id', '=', uid)]"/>
|
||||
<separator/>
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<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="//div[@id='utm_statistics']" position="inside">
|
||||
<div class="d-flex justify-content-between align-items-center w-100 order-2 mb-1">
|
||||
<field name="currency_id" invisible="True"/>
|
||||
<strong>Revenues</strong> <span><field name="invoiced_amount" widget="monetary" options="{'currency_field': 'currency_id'}"/></span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between align-items-center w-100 order-4 mb-1">
|
||||
<strong>Quotations</strong> <span><field name="quotation_count"/></span>
|
||||
</div>
|
||||
</xpath>
|
||||
</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="inherit_id" ref="utm.utm_campaign_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//div[hasclass('oe_button_box')]" position="inside">
|
||||
<button name="action_redirect_to_invoiced"
|
||||
type="object" class="oe_stat_button order-1" icon="fa-money">
|
||||
<field name="invoiced_amount" widget="statinfo" string="Revenues"/>
|
||||
</button>
|
||||
<button name="action_redirect_to_quotations"
|
||||
type="object" class="oe_stat_button order-2" icon="fa-usd">
|
||||
<field name="quotation_count" widget="statinfo" string="Quotations"/>
|
||||
</button>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -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)
|
||||
|
||||
@@ -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',
|
||||
],
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 !"),
|
||||
]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
@@ -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>
|
||||
@@ -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
|
||||
|
||||
@@ -77,10 +77,16 @@ var SelectBox = publicWidget.Widget.extend({
|
||||
*/
|
||||
_createObject: function (name) {
|
||||
var self = this;
|
||||
var args = {
|
||||
name: name
|
||||
};
|
||||
if (this.obj === "utm.campaign"){
|
||||
args.is_website = true;
|
||||
}
|
||||
return this._rpc({
|
||||
model: this.obj,
|
||||
method: 'create',
|
||||
args: [{name: name}],
|
||||
args: [args],
|
||||
}).then(function (record) {
|
||||
self.$el.attr('value', record);
|
||||
self.objects.push({'id': record, 'text': name});
|
||||
|
||||
Reference in New Issue
Block a user