-
diff --git a/addons/mass_mailing/views/utm_campaign_views.xml b/addons/mass_mailing/views/utm_campaign_views.xml
new file mode 100644
index 00000000000..0523ad7fca2
--- /dev/null
+++ b/addons/mass_mailing/views/utm_campaign_views.xml
@@ -0,0 +1,126 @@
+
+
+
+ utm.campaign.view.form
+ utm.campaign
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ utm.campaign.view.kanban
+ utm.campaign
+
+
+
+
+
+
+
+ Mailings
+
+
+
+
+
+
+ UTM Campaigns
+ utm.campaign
+ kanban,tree,form
+
+
+ Create a new UTM campaign
+
+ Create a campaign to structure mailing and get analysis from email status.
+
+
+ [('is_website', '=', False)]
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/addons/mass_mailing/wizard/mail_compose_message.py b/addons/mass_mailing/wizard/mail_compose_message.py
index d8be92aafc4..18b09bcd4ce 100644
--- a/addons/mass_mailing/wizard/mail_compose_message.py
+++ b/addons/mass_mailing/wizard/mail_compose_message.py
@@ -8,8 +8,8 @@ from odoo.tools import email_re
class MailComposeMessage(models.TransientModel):
_inherit = 'mail.compose.message'
- mass_mailing_campaign_id = fields.Many2one('mail.mass_mailing.campaign', string='Mass Mailing Campaign')
mass_mailing_id = fields.Many2one('mailing.mailing', string='Mass Mailing', ondelete='cascade')
+ campaign_id = fields.Many2one('utm.campaign', string='Mass Mailing Campaign')
mass_mailing_name = fields.Char(string='Mass Mailing Name')
mailing_list_ids = fields.Many2many('mailing.list', string='Mailing List')
@@ -28,7 +28,7 @@ class MailComposeMessage(models.TransientModel):
reply_to_mode = 'email' if self.no_auto_thread else 'thread'
reply_to = self.reply_to if self.no_auto_thread else False
mass_mailing = self.env['mailing.mailing'].create({
- 'mass_mailing_campaign_id': self.mass_mailing_campaign_id.id,
+ 'campaign_id': self.campaign_id.id,
'name': self.mass_mailing_name,
'template_id': self.template_id.id,
'state': 'done',
diff --git a/addons/mass_mailing/wizard/mail_compose_message_views.xml b/addons/mass_mailing/wizard/mail_compose_message_views.xml
index e6abc5e1fe1..07aec46f4a5 100644
--- a/addons/mass_mailing/wizard/mail_compose_message_views.xml
+++ b/addons/mass_mailing/wizard/mail_compose_message_views.xml
@@ -8,7 +8,7 @@
-
diff --git a/addons/mass_mailing_crm/data/mass_mailing_demo.xml b/addons/mass_mailing_crm/data/mass_mailing_demo.xml
index b124fbbc66c..8e7eac4f6d0 100644
--- a/addons/mass_mailing_crm/data/mass_mailing_demo.xml
+++ b/addons/mass_mailing_crm/data/mass_mailing_demo.xml
@@ -7,8 +7,7 @@
in_queue
-
-
+
[]
diff --git a/addons/mass_mailing_sale/data/mass_mailing_demo.xml b/addons/mass_mailing_sale/data/mass_mailing_demo.xml
index 5e6daf0957b..6b2b543e790 100644
--- a/addons/mass_mailing_sale/data/mass_mailing_demo.xml
+++ b/addons/mass_mailing_sale/data/mass_mailing_demo.xml
@@ -7,8 +7,7 @@
in_queue
-
-
+
[]
diff --git a/addons/mass_mailing_sale/models/mailing_mailing.py b/addons/mass_mailing_sale/models/mailing_mailing.py
index 2a347a3694a..08716037a55 100644
--- a/addons/mass_mailing_sale/models/mailing_mailing.py
+++ b/addons/mass_mailing_sale/models/mailing_mailing.py
@@ -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
diff --git a/addons/sale/__manifest__.py b/addons/sale/__manifest__.py
index e0209fbf701..5ec327926df 100644
--- a/addons/sale/__manifest__.py
+++ b/addons/sale/__manifest__.py
@@ -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',
diff --git a/addons/sale/models/__init__.py b/addons/sale/models/__init__.py
index 738285ffcde..bb1ddbacd04 100644
--- a/addons/sale/models/__init__.py
+++ b/addons/sale/models/__init__.py
@@ -13,3 +13,4 @@ from . import sale
from . import res_config_settings
from . import sales_team
from . import payment
+from . import utm
diff --git a/addons/sale/models/utm.py b/addons/sale/models/utm.py
new file mode 100644
index 00000000000..f94e26de7c0
--- /dev/null
+++ b/addons/sale/models/utm.py
@@ -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
diff --git a/addons/sale/views/sale_views.xml b/addons/sale/views/sale_views.xml
index 176910533a6..e76a6336c41 100644
--- a/addons/sale/views/sale_views.xml
+++ b/addons/sale/views/sale_views.xml
@@ -701,6 +701,7 @@
+
diff --git a/addons/sale/views/utm_campaign_views.xml b/addons/sale/views/utm_campaign_views.xml
new file mode 100644
index 00000000000..943a951752d
--- /dev/null
+++ b/addons/sale/views/utm_campaign_views.xml
@@ -0,0 +1,37 @@
+
+
+
+ utm.campaign.view.kanban
+ utm.campaign
+
+
+
+
+
+ Revenues
+
+
+ Quotations
+
+
+
+
+
+
+ utm.campaign.view.form
+ utm.campaign
+
+
+
+
+
+
+
+
+
diff --git a/addons/test_mass_mailing/tests/test_performance.py b/addons/test_mass_mailing/tests/test_performance.py
index b3ec164653d..7fd2d36ab13 100644
--- a/addons/test_mass_mailing/tests/test_performance.py
+++ b/addons/test_mass_mailing/tests/test_performance.py
@@ -50,7 +50,7 @@ class TestMassMailPerformance(TestMassMailPerformanceBase):
'mailing_domain': [('id', 'in', self.mm_recs.ids)],
})
- with self.assertQueryCount(__system__=2477, marketing=3128):
+ with self.assertQueryCount(__system__=2478, marketing=3128):
mailing.action_send_mail()
self.assertEqual(mailing.sent, 50)
@@ -88,7 +88,7 @@ class TestMassMailBlPerformance(TestMassMailPerformanceBase):
'mailing_domain': [('id', 'in', self.mm_recs.ids)],
})
- with self.assertQueryCount(__system__=2864, marketing=3611):
+ with self.assertQueryCount(__system__=2865, marketing=3611):
mailing.action_send_mail()
self.assertEqual(mailing.sent, 50)
diff --git a/addons/utm/__manifest__.py b/addons/utm/__manifest__.py
index ffa06da03bf..b7f56a957a9 100644
--- a/addons/utm/__manifest__.py
+++ b/addons/utm/__manifest__.py
@@ -11,6 +11,7 @@ Enable management of UTM trackers: campaign, medium, source.
'depends': ['base'],
'data': [
'data/utm_data.xml',
+ 'views/utm_campaign_views.xml',
'views/utm_views.xml',
'security/ir.model.access.csv',
],
diff --git a/addons/utm/data/utm_data.xml b/addons/utm/data/utm_data.xml
index 137dceb7a15..b8c77be0dc7 100644
--- a/addons/utm/data/utm_data.xml
+++ b/addons/utm/data/utm_data.xml
@@ -1,17 +1,43 @@
+
+
+ Default
+ 10
+
+
+ Schedule
+ 10
+
+
+ Design
+ 20
+
+
+ Sent
+ 30
+
+
Sale
+
+
Christmas Special
+
+
Email Campaign - Services
+
+
Email Campaign - Products
+
+
@@ -62,4 +88,10 @@
Google Adwords
+
+
+
+ Marketing
+
+
diff --git a/addons/utm/models/utm.py b/addons/utm/models/utm.py
index 92046d17bea..3c197d1814e 100644
--- a/addons/utm/models/utm.py
+++ b/addons/utm/models/utm.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-from odoo import fields, models
+from odoo import fields, models, api, SUPERUSER_ID
class UtmMedium(models.Model):
@@ -21,9 +21,52 @@ class UtmCampaign(models.Model):
name = fields.Char(string='Campaign Name', required=True, translate=True)
+ user_id = fields.Many2one(
+ 'res.users', string='Responsible',
+ required=True, default=lambda self: self.env.uid)
+ stage_id = fields.Many2one('utm.stage', string='Stage', ondelete='restrict', required=True,
+ default=lambda self: self.env['utm.stage'].search([], limit=1),
+ group_expand='_group_expand_stage_ids')
+ tag_ids = fields.Many2many(
+ 'utm.tag', 'utm_tag_rel',
+ 'tag_id', 'campaign_id', string='Tags')
+
+ is_website = fields.Boolean(default=False, help="Allows us to filter relevant Campaign")
+ color = fields.Integer(string='Color Index')
+
+ @api.model
+ def _group_expand_stage_ids(self, stages, domain, order):
+ """ Read group customization in order to display all the stages in the
+ kanban view, even if they are empty
+ """
+ stage_ids = stages._search([], order=order, access_rights_uid=SUPERUSER_ID)
+ return stages.browse(stage_ids)
class UtmSource(models.Model):
_name = 'utm.source'
_description = 'UTM Source'
name = fields.Char(string='Source Name', required=True, translate=True)
+
+class UtmStage(models.Model):
+
+ """Stage for utm campaigns. """
+ _name = 'utm.stage'
+ _description = 'Campaign Stage'
+ _order = 'sequence'
+
+ name = fields.Char(required=True, translate=True)
+ sequence = fields.Integer()
+
+class UtmTag(models.Model):
+ """Model of categories of utm campaigns, i.e. marketing, newsletter, ... """
+ _name = 'utm.tag'
+ _description = 'UTM Tag'
+ _order = 'name'
+
+ name = fields.Char(required=True, translate=True)
+ color = fields.Integer(string='Color Index')
+
+ _sql_constraints = [
+ ('name_uniq', 'unique (name)', "Tag name already exists !"),
+ ]
diff --git a/addons/utm/models/utm_mixin.py b/addons/utm/models/utm_mixin.py
index e77325d32c3..1b4be171324 100644
--- a/addons/utm/models/utm_mixin.py
+++ b/addons/utm/models/utm_mixin.py
@@ -37,7 +37,7 @@ class UtmMixin(models.AbstractModel):
Model = self.env[field.comodel_name]
records = Model.search([('name', '=', value)], limit=1)
if not records:
- records = Model.create({'name': value})
+ records = Model.create({'name': value, 'is_website': True})
value = records.id
if value:
values[field_name] = value
diff --git a/addons/utm/security/ir.model.access.csv b/addons/utm/security/ir.model.access.csv
index 3e31842f9d9..3602e9c8d5b 100644
--- a/addons/utm/security/ir.model.access.csv
+++ b/addons/utm/security/ir.model.access.csv
@@ -1,7 +1,12 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_utm_campaign_user,access_utm_campaign_user,model_utm_campaign,base.group_user,1,1,1,0
+access_utm_campaign_system,utm.campaign.system,model_utm_campaign,base.group_system,1,1,1,1
access_utm_medium_user,access_utm_medium_user,model_utm_medium,base.group_user,1,1,1,0
access_utm_source_user,access_utm_source_user,model_utm_source,base.group_user,1,1,1,0
+access_utm_stage_user,mail.utm.stage,model_utm_stage,base.group_user,1,0,0,0
+access_utm_stage_system,mail.utm.stage,model_utm_stage,base.group_system,1,1,1,1
access_utm_campaign,access_utm_campaign,model_utm_campaign,,1,0,1,0
access_utm_medium,access_utm_medium,model_utm_medium,,1,0,1,0
access_utm_source,access_utm_source,model_utm_source,,1,0,1,0
+access_utm_tag_user,utm.tag,model_utm_tag,base.group_user,1,0,0,0
+access_utm_tag_system,utm.tag,model_utm_tag,base.group_system,1,1,1,1
diff --git a/addons/utm/views/utm_campaign_views.xml b/addons/utm/views/utm_campaign_views.xml
new file mode 100644
index 00000000000..e333d1592f7
--- /dev/null
+++ b/addons/utm/views/utm_campaign_views.xml
@@ -0,0 +1,160 @@
+
+
+
+ utm.campaign.view.search
+ utm.campaign
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ utm.campaign.view.form
+ utm.campaign
+
+
+
+
+
+
+ utm.campaign.view.tree
+ utm.campaign
+
+
+
+
+
+
+
+
+
+
+
+ utm.campaign.view.kanban
+ utm.campaign
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ utm.tag.view.form
+ utm.tag
+
+
+
+
+
+
+ Campaign Tags
+ utm.tag
+
+
+ Add a new tag
+
+
+
+
+
+
+ utm.stage.view.search
+ utm.stage
+
+
+
+
+
+
+
+
+ utm.stage.view.tree
+ utm.stage
+ 10
+
+
+
+
+
+
+
+
+
+ UTM Stages
+ utm.stage
+ tree,form
+
+
+ Create a new utm stage
+
+
+
+
\ No newline at end of file
diff --git a/addons/utm/views/utm_views.xml b/addons/utm/views/utm_views.xml
index b2f5ccbea74..8bf401e0de3 100644
--- a/addons/utm/views/utm_views.xml
+++ b/addons/utm/views/utm_views.xml
@@ -11,34 +11,10 @@
sequence="99"
groups="base.group_no_one"/>
-
-
- utm.campaign.view.tree
- utm.campaign
-
-
-
-
-
-
-
- utm.campaign.view.form
- utm.campaign
-
-
-
-
-
Campaigns
utm.campaign
-
+ kanban,tree,form
Define a new UTM campaign
diff --git a/addons/website_links/static/src/js/website_links.js b/addons/website_links/static/src/js/website_links.js
index 7ebb60c0c2c..91b7177ea6b 100644
--- a/addons/website_links/static/src/js/website_links.js
+++ b/addons/website_links/static/src/js/website_links.js
@@ -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});