summaryrefslogtreecommitdiff
path: root/addons/mass_mailing_sms/models
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/mass_mailing_sms/models
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/mass_mailing_sms/models')
-rw-r--r--addons/mass_mailing_sms/models/__init__.py10
-rw-r--r--addons/mass_mailing_sms/models/mailing_contact.py15
-rw-r--r--addons/mass_mailing_sms/models/mailing_list.py36
-rw-r--r--addons/mass_mailing_sms/models/mailing_mailing.py279
-rw-r--r--addons/mass_mailing_sms/models/mailing_trace.py87
-rw-r--r--addons/mass_mailing_sms/models/res_users.py72
-rw-r--r--addons/mass_mailing_sms/models/sms_sms.py44
-rw-r--r--addons/mass_mailing_sms/models/utm.py42
8 files changed, 585 insertions, 0 deletions
diff --git a/addons/mass_mailing_sms/models/__init__.py b/addons/mass_mailing_sms/models/__init__.py
new file mode 100644
index 00000000..1f0a7eb2
--- /dev/null
+++ b/addons/mass_mailing_sms/models/__init__.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from . import mailing_contact
+from . import mailing_list
+from . import mailing_mailing
+from . import mailing_trace
+from . import res_users
+from . import sms_sms
+from . import utm
diff --git a/addons/mass_mailing_sms/models/mailing_contact.py b/addons/mass_mailing_sms/models/mailing_contact.py
new file mode 100644
index 00000000..73a3431e
--- /dev/null
+++ b/addons/mass_mailing_sms/models/mailing_contact.py
@@ -0,0 +1,15 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import fields, models
+
+
+class MailingContact(models.Model):
+ _name = 'mailing.contact'
+ _inherit = ['mailing.contact', 'mail.thread.phone']
+
+ mobile = fields.Char(string='Mobile')
+
+ def _sms_get_number_fields(self):
+ # TDE note: should override _phone_get_number_fields but ok as sms is in dependencies
+ return ['mobile']
diff --git a/addons/mass_mailing_sms/models/mailing_list.py b/addons/mass_mailing_sms/models/mailing_list.py
new file mode 100644
index 00000000..9e48fa7d
--- /dev/null
+++ b/addons/mass_mailing_sms/models/mailing_list.py
@@ -0,0 +1,36 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import models
+
+
+class MailingList(models.Model):
+ _inherit = 'mailing.list'
+
+ def _compute_contact_nbr(self):
+ if self.env.context.get('mailing_sms') and self.ids:
+ self.env.cr.execute('''
+select list_id, count(*)
+from mailing_contact_list_rel r
+left join mailing_contact c on (r.contact_id=c.id)
+left join phone_blacklist bl on c.phone_sanitized = bl.number and bl.active
+where
+ list_id in %s
+ AND COALESCE(r.opt_out,FALSE) = FALSE
+ AND c.phone_sanitized IS NOT NULL
+ AND bl.id IS NULL
+group by list_id''', (tuple(self.ids), ))
+ data = dict(self.env.cr.fetchall())
+ for mailing_list in self:
+ mailing_list.contact_nbr = data.get(mailing_list.id, 0)
+ return
+ return super(MailingList, self)._compute_contact_nbr()
+
+ def action_view_contacts(self):
+ if self.env.context.get('mailing_sms'):
+ action = self.env["ir.actions.actions"]._for_xml_id("mass_mailing_sms.mailing_contact_action_sms")
+ action['domain'] = [('list_ids', 'in', self.ids)]
+ context = dict(self.env.context, search_default_filter_valid_sms_recipient=1, default_list_ids=self.ids)
+ action['context'] = context
+ return action
+ return super(MailingList, self).action_view_contacts()
diff --git a/addons/mass_mailing_sms/models/mailing_mailing.py b/addons/mass_mailing_sms/models/mailing_mailing.py
new file mode 100644
index 00000000..ea2e36d7
--- /dev/null
+++ b/addons/mass_mailing_sms/models/mailing_mailing.py
@@ -0,0 +1,279 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+import logging
+
+from odoo import api, fields, models, _
+from odoo.exceptions import UserError
+from odoo.osv import expression
+
+_logger = logging.getLogger(__name__)
+
+
+class Mailing(models.Model):
+ _inherit = 'mailing.mailing'
+
+ @api.model
+ def default_get(self, fields):
+ res = super(Mailing, self).default_get(fields)
+ if fields is not None and 'keep_archives' in fields and res.get('mailing_type') == 'sms':
+ res['keep_archives'] = True
+ return res
+
+ # mailing options
+ mailing_type = fields.Selection(selection_add=[
+ ('sms', 'SMS')
+ ], ondelete={'sms': 'set default'})
+
+ # 'sms_subject' added to override 'subject' field (string attribute should be labelled "Title" when mailing_type == 'sms').
+ # 'sms_subject' should have the same helper as 'subject' field when 'mass_mailing_sms' installed.
+ # otherwise 'sms_subject' will get the old helper from 'mass_mailing' module.
+ # overriding 'subject' field helper in this model is not working, since the helper will keep the new value
+ # even when 'mass_mailing_sms' removed (see 'mailing_mailing_view_form_sms' for more details).
+ sms_subject = fields.Char('Title', help='For an email, the subject your recipients will see in their inbox.\n'
+ 'For an SMS, the internal title of the message.',
+ related='subject', translate=True, readonly=False)
+ # sms options
+ body_plaintext = fields.Text('SMS Body', compute='_compute_body_plaintext', store=True, readonly=False)
+ sms_template_id = fields.Many2one('sms.template', string='SMS Template', ondelete='set null')
+ sms_has_insufficient_credit = fields.Boolean(
+ 'Insufficient IAP credits', compute='_compute_sms_has_iap_failure',
+ help='UX Field to propose to buy IAP credits')
+ sms_has_unregistered_account = fields.Boolean(
+ 'Unregistered IAP account', compute='_compute_sms_has_iap_failure',
+ help='UX Field to propose to Register the SMS IAP account')
+ sms_force_send = fields.Boolean(
+ 'Send Directly', help='Use at your own risks.')
+ # opt_out_link
+ sms_allow_unsubscribe = fields.Boolean('Include opt-out link', default=False)
+
+ @api.depends('mailing_type')
+ def _compute_medium_id(self):
+ super(Mailing, self)._compute_medium_id()
+ for mailing in self:
+ if mailing.mailing_type == 'sms' and (not mailing.medium_id or mailing.medium_id == self.env.ref('utm.utm_medium_email')):
+ mailing.medium_id = self.env.ref('mass_mailing_sms.utm_medium_sms').id
+ elif mailing.mailing_type == 'mail' and (not mailing.medium_id or mailing.medium_id == self.env.ref('mass_mailing_sms.utm_medium_sms')):
+ mailing.medium_id = self.env.ref('utm.utm_medium_email').id
+
+ @api.depends('sms_template_id', 'mailing_type')
+ def _compute_body_plaintext(self):
+ for mailing in self:
+ if mailing.mailing_type == 'sms' and mailing.sms_template_id:
+ mailing.body_plaintext = mailing.sms_template_id.body
+
+ @api.depends('mailing_trace_ids.failure_type')
+ def _compute_sms_has_iap_failure(self):
+ failures = ['sms_acc', 'sms_credit']
+ if not self.ids:
+ self.sms_has_insufficient_credit = self.sms_has_unregistered_account = False
+ else:
+ traces = self.env['mailing.trace'].sudo().read_group([
+ ('mass_mailing_id', 'in', self.ids),
+ ('trace_type', '=', 'sms'),
+ ('failure_type', 'in', failures)
+ ], ['mass_mailing_id', 'failure_type'], ['mass_mailing_id', 'failure_type'], lazy=False)
+
+ trace_dict = dict.fromkeys(self.ids, {key: False for key in failures})
+ for t in traces:
+ trace_dict[t['mass_mailing_id'][0]][t['failure_type']] = t['__count'] and True or False
+
+ for mail in self:
+ mail.sms_has_insufficient_credit = trace_dict[mail.id]['sms_credit']
+ mail.sms_has_unregistered_account = trace_dict[mail.id]['sms_acc']
+
+
+ # --------------------------------------------------
+ # ORM OVERRIDES
+ # --------------------------------------------------
+
+ @api.model
+ def create(self, values):
+ # Get subject from "sms_subject" field when SMS installed (used to build the name of record in the super 'create' method)
+ if values.get('mailing_type') == 'sms' and values.get('sms_subject'):
+ values['subject'] = values['sms_subject']
+ return super(Mailing, self).create(values)
+
+ # --------------------------------------------------
+ # BUSINESS / VIEWS ACTIONS
+ # --------------------------------------------------
+
+ def action_put_in_queue_sms(self):
+ res = self.action_put_in_queue()
+ if self.sms_force_send:
+ self.action_send_mail()
+ return res
+
+ def action_send_now_sms(self):
+ if not self.sms_force_send:
+ self.write({'sms_force_send': True})
+ return self.action_send_mail()
+
+ def action_retry_failed(self):
+ mass_sms = self.filtered(lambda m: m.mailing_type == 'sms')
+ if mass_sms:
+ mass_sms.action_retry_failed_sms()
+ return super(Mailing, self - mass_sms).action_retry_failed()
+
+ def action_retry_failed_sms(self):
+ failed_sms = self.env['sms.sms'].sudo().search([
+ ('mailing_id', 'in', self.ids),
+ ('state', '=', 'error')
+ ])
+ failed_sms.mapped('mailing_trace_ids').unlink()
+ failed_sms.unlink()
+ self.write({'state': 'in_queue'})
+
+ def action_test(self):
+ if self.mailing_type == 'sms':
+ ctx = dict(self.env.context, default_mailing_id=self.id)
+ return {
+ 'name': _('Test SMS marketing'),
+ 'type': 'ir.actions.act_window',
+ 'view_mode': 'form',
+ 'res_model': 'mailing.sms.test',
+ 'target': 'new',
+ 'context': ctx,
+ }
+ return super(Mailing, self).action_test()
+
+ def _action_view_traces_filtered(self, view_filter):
+ action = super(Mailing, self)._action_view_traces_filtered(view_filter)
+ if self.mailing_type == 'sms':
+ action['views'] = [(self.env.ref('mass_mailing_sms.mailing_trace_view_tree_sms').id, 'tree'),
+ (self.env.ref('mass_mailing_sms.mailing_trace_view_form_sms').id, 'form')]
+ return action
+
+ def action_buy_sms_credits(self):
+ url = self.env['iap.account'].get_credits_url(service_name='sms')
+ return {
+ 'type': 'ir.actions.act_url',
+ 'url': url,
+ }
+
+ # --------------------------------------------------
+ # SMS SEND
+ # --------------------------------------------------
+
+ def _get_opt_out_list_sms(self):
+ """Returns a set of emails opted-out in target model"""
+ self.ensure_one()
+ opt_out = []
+ target = self.env[self.mailing_model_real]
+ if self.mailing_model_real == 'mailing.contact':
+ # if user is opt_out on One list but not on another
+ # or if two user with same email address, one opted in and the other one opted out, send the mail anyway
+ # TODO DBE Fixme : Optimise the following to get real opt_out and opt_in
+ subscriptions = self.env['mailing.contact.subscription'].sudo().search(
+ [('list_id', 'in', self.contact_list_ids.ids)])
+ opt_out_contacts = subscriptions.filtered(lambda sub: sub.opt_out).mapped('contact_id')
+ opt_in_contacts = subscriptions.filtered(lambda sub: not sub.opt_out).mapped('contact_id')
+ opt_out = list(set(c.id for c in opt_out_contacts if c not in opt_in_contacts))
+
+ _logger.info("Mass SMS %s targets %s: optout: %s contacts", self, target._name, len(opt_out))
+ else:
+ _logger.info("Mass SMS %s targets %s: no opt out list available", self, target._name)
+ return opt_out
+
+ def _get_seen_list_sms(self):
+ """Returns a set of emails already targeted by current mailing/campaign (no duplicates)"""
+ self.ensure_one()
+ target = self.env[self.mailing_model_real]
+
+ partner_fields = []
+ if issubclass(type(target), self.pool['mail.thread.phone']):
+ phone_fields = ['phone_sanitized']
+ elif issubclass(type(target), self.pool['mail.thread']):
+ phone_fields = target._sms_get_number_fields()
+ partner_fields = target._sms_get_partner_fields()
+ else:
+ phone_fields = []
+ if 'mobile' in target._fields:
+ phone_fields.append('mobile')
+ if 'phone' in target._fields:
+ phone_fields.append('phone')
+ partner_field = next(
+ (fname for fname in partner_fields if target._fields[fname].type == 'many2one'),
+ False
+ )
+ if not phone_fields and not partner_field:
+ raise UserError(_("Unsupported %s for mass SMS", self.mailing_model_id.name))
+
+ query = """
+ SELECT %(select_query)s
+ FROM mailing_trace trace
+ JOIN %(target_table)s target ON (trace.res_id = target.id)
+ %(join_add_query)s
+ WHERE (%(where_query)s)
+ AND trace.mass_mailing_id = %%(mailing_id)s
+ AND trace.model = %%(target_model)s
+ """
+ if phone_fields:
+ # phone fields are checked on target mailed model
+ select_query = 'target.id, ' + ', '.join('target.%s' % fname for fname in phone_fields)
+ where_query = ' OR '.join('target.%s IS NOT NULL' % fname for fname in phone_fields)
+ join_add_query = ''
+ else:
+ # phone fields are checked on res.partner model
+ partner_phone_fields = ['mobile', 'phone']
+ select_query = 'target.id, ' + ', '.join('partner.%s' % fname for fname in partner_phone_fields)
+ where_query = ' OR '.join('partner.%s IS NOT NULL' % fname for fname in partner_phone_fields)
+ join_add_query = 'JOIN res_partner partner ON (target.%s = partner.id)' % partner_field
+
+ query = query % {
+ 'select_query': select_query,
+ 'where_query': where_query,
+ 'target_table': target._table,
+ 'join_add_query': join_add_query,
+ }
+ params = {'mailing_id': self.id, 'target_model': self.mailing_model_real}
+ self._cr.execute(query, params)
+ query_res = self._cr.fetchall()
+ seen_list = set(number for item in query_res for number in item[1:] if number)
+ seen_ids = set(item[0] for item in query_res)
+ _logger.info("Mass SMS %s targets %s: already reached %s SMS", self, target._name, len(seen_list))
+ return list(seen_ids), list(seen_list)
+
+ def _send_sms_get_composer_values(self, res_ids):
+ return {
+ # content
+ 'body': self.body_plaintext,
+ 'template_id': self.sms_template_id.id,
+ 'res_model': self.mailing_model_real,
+ 'res_ids': repr(res_ids),
+ # options
+ 'composition_mode': 'mass',
+ 'mailing_id': self.id,
+ 'mass_keep_log': self.keep_archives,
+ 'mass_force_send': self.sms_force_send,
+ 'mass_sms_allow_unsubscribe': self.sms_allow_unsubscribe,
+ }
+
+ def action_send_mail(self, res_ids=None):
+ mass_sms = self.filtered(lambda m: m.mailing_type == 'sms')
+ if mass_sms:
+ mass_sms.action_send_sms(res_ids=res_ids)
+ return super(Mailing, self - mass_sms).action_send_mail(res_ids=res_ids)
+
+ def action_send_sms(self, res_ids=None):
+ for mailing in self:
+ if not res_ids:
+ res_ids = mailing._get_remaining_recipients()
+ if not res_ids:
+ raise UserError(_('There are no recipients selected.'))
+
+ composer = self.env['sms.composer'].with_context(active_id=False).create(mailing._send_sms_get_composer_values(res_ids))
+ composer._action_send_sms()
+ mailing.write({'state': 'done', 'sent_date': fields.Datetime.now()})
+ return True
+
+ # --------------------------------------------------
+ # TOOLS
+ # --------------------------------------------------
+
+ def _get_default_mailing_domain(self):
+ mailing_domain = super(Mailing, self)._get_default_mailing_domain()
+ if self.mailing_type == 'sms' and 'phone_sanitized_blacklisted' in self.env[self.mailing_model_name]._fields:
+ mailing_domain = expression.AND([mailing_domain, [('phone_sanitized_blacklisted', '=', False)]])
+
+ return mailing_domain
diff --git a/addons/mass_mailing_sms/models/mailing_trace.py b/addons/mass_mailing_sms/models/mailing_trace.py
new file mode 100644
index 00000000..743afe14
--- /dev/null
+++ b/addons/mass_mailing_sms/models/mailing_trace.py
@@ -0,0 +1,87 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+import random
+import string
+
+from odoo import api, fields, models
+from odoo.osv import expression
+
+
+class MailingTrace(models.Model):
+ """ Improve statistics model to add SMS support. Main attributes of
+ statistics model are used, only some specific data is required. """
+ _inherit = 'mailing.trace'
+ CODE_SIZE = 3
+
+ trace_type = fields.Selection(selection_add=[
+ ('sms', 'SMS')
+ ], ondelete={'sms': 'set default'})
+ sms_sms_id = fields.Many2one('sms.sms', string='SMS', index=True, ondelete='set null')
+ sms_sms_id_int = fields.Integer(
+ string='SMS ID (tech)',
+ help='ID of the related sms.sms. This field is an integer field because '
+ 'the related sms.sms can be deleted separately from its statistics. '
+ 'However the ID is needed for several action and controllers.',
+ index=True,
+ )
+ sms_number = fields.Char('Number')
+ sms_code = fields.Char('Code')
+ failure_type = fields.Selection(selection_add=[
+ ('sms_number_missing', 'Missing Number'),
+ ('sms_number_format', 'Wrong Number Format'),
+ ('sms_credit', 'Insufficient Credit'),
+ ('sms_server', 'Server Error'),
+ ('sms_acc', 'Unregistered Account'),
+ # mass mode specific codes
+ ('sms_blacklist', 'Blacklisted'),
+ ('sms_duplicate', 'Duplicate'),
+ ])
+
+ @api.model_create_multi
+ def create(self, values_list):
+ for values in values_list:
+ if 'sms_sms_id' in values:
+ values['sms_sms_id_int'] = values['sms_sms_id']
+ if values.get('trace_type') == 'sms' and not values.get('sms_code'):
+ values['sms_code'] = self._get_random_code()
+ return super(MailingTrace, self).create(values_list)
+
+ def _get_random_code(self):
+ """ Generate a random code for trace. Uniqueness is not really necessary
+ as it serves as obfuscation when unsubscribing. A valid trio
+ code / mailing_id / number will be requested. """
+ return ''.join(random.choice(string.ascii_letters + string.digits) for dummy in range(self.CODE_SIZE))
+
+ def _get_records_from_sms(self, sms_sms_ids=None, additional_domain=None):
+ if not self.ids and sms_sms_ids:
+ domain = [('sms_sms_id_int', 'in', sms_sms_ids)]
+ else:
+ domain = [('id', 'in', self.ids)]
+ if additional_domain:
+ domain = expression.AND([domain, additional_domain])
+ return self.search(domain)
+
+ def set_failed(self, failure_type):
+ for trace in self:
+ trace.write({'exception': fields.Datetime.now(), 'failure_type': failure_type})
+
+ def set_sms_sent(self, sms_sms_ids=None):
+ statistics = self._get_records_from_sms(sms_sms_ids, [('sent', '=', False)])
+ statistics.write({'sent': fields.Datetime.now()})
+ return statistics
+
+ def set_sms_clicked(self, sms_sms_ids=None):
+ statistics = self._get_records_from_sms(sms_sms_ids, [('clicked', '=', False)])
+ statistics.write({'clicked': fields.Datetime.now()})
+ return statistics
+
+ def set_sms_ignored(self, sms_sms_ids=None):
+ statistics = self._get_records_from_sms(sms_sms_ids, [('ignored', '=', False)])
+ statistics.write({'ignored': fields.Datetime.now()})
+ return statistics
+
+ def set_sms_exception(self, sms_sms_ids=None):
+ statistics = self._get_records_from_sms(sms_sms_ids, [('exception', '=', False)])
+ statistics.write({'exception': fields.Datetime.now()})
+ return statistics
diff --git a/addons/mass_mailing_sms/models/res_users.py b/addons/mass_mailing_sms/models/res_users.py
new file mode 100644
index 00000000..8c23887c
--- /dev/null
+++ b/addons/mass_mailing_sms/models/res_users.py
@@ -0,0 +1,72 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+import json
+
+from odoo import api, fields, models, modules, _
+
+
+class Users(models.Model):
+ _name = 'res.users'
+ _inherit = ['res.users']
+
+ @api.model
+ def systray_get_activities(self):
+ """ Split mass_mailing and mass_mailing_sms activities in systray by
+ removing the single mailing.mailing activity represented and
+ doing a new query to split them by mailing_type.
+ """
+ activities = super(Users, self).systray_get_activities()
+ for activity in activities:
+ if activity.get('model') == 'mailing.mailing':
+ activities.remove(activity)
+ query = """SELECT m.mailing_type, count(*), act.res_model as model, act.res_id,
+ CASE
+ WHEN %(today)s::date - act.date_deadline::date = 0 Then 'today'
+ WHEN %(today)s::date - act.date_deadline::date > 0 Then 'overdue'
+ WHEN %(today)s::date - act.date_deadline::date < 0 Then 'planned'
+ END AS states
+ FROM mail_activity AS act
+ JOIN mailing_mailing AS m ON act.res_id = m.id
+ WHERE act.res_model = 'mailing.mailing' AND act.user_id = %(user_id)s
+ GROUP BY m.mailing_type, states, act.res_model, act.res_id;
+ """
+ self.env.cr.execute(query, {
+ 'today': fields.Date.context_today(self),
+ 'user_id': self.env.uid,
+ })
+ activity_data = self.env.cr.dictfetchall()
+
+ user_activities = {}
+ for act in activity_data:
+ if not user_activities.get(act['mailing_type']):
+ if act['mailing_type'] == 'sms':
+ module = 'mass_mailing_sms'
+ name = _('SMS Marketing')
+ else:
+ module = 'mass_mailing'
+ name = _('Email Marketing')
+ icon = module and modules.module.get_module_icon(module)
+ res_ids = set()
+ user_activities[act['mailing_type']] = {
+ 'name': name,
+ 'model': 'mailing.mailing',
+ 'type': 'activity',
+ 'icon': icon,
+ 'total_count': 0, 'today_count': 0, 'overdue_count': 0, 'planned_count': 0,
+ 'res_ids': res_ids,
+ }
+ user_activities[act['mailing_type']]['res_ids'].add(act['res_id'])
+ user_activities[act['mailing_type']]['%s_count' % act['states']] += act['count']
+ if act['states'] in ('today', 'overdue'):
+ user_activities[act['mailing_type']]['total_count'] += act['count']
+
+ for mailing_type in user_activities.keys():
+ user_activities[mailing_type].update({
+ 'actions': [{'icon': 'fa-clock-o', 'name': 'Summary',}],
+ 'domain': json.dumps([['activity_ids.res_id', 'in', list(user_activities[mailing_type]['res_ids'])]])
+ })
+ activities.extend(list(user_activities.values()))
+ break
+
+ return activities
diff --git a/addons/mass_mailing_sms/models/sms_sms.py b/addons/mass_mailing_sms/models/sms_sms.py
new file mode 100644
index 00000000..c9c14e14
--- /dev/null
+++ b/addons/mass_mailing_sms/models/sms_sms.py
@@ -0,0 +1,44 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+import re
+
+from odoo import fields, models, tools
+
+
+class SmsSms(models.Model):
+ _inherit = ['sms.sms']
+
+ mailing_id = fields.Many2one('mailing.mailing', string='Mass Mailing')
+ mailing_trace_ids = fields.One2many('mailing.trace', 'sms_sms_id', string='Statistics')
+
+ def _update_body_short_links(self):
+ """ Override to tweak shortened URLs by adding statistics ids, allowing to
+ find customer back once clicked. """
+ shortened_schema = self.env['ir.config_parameter'].sudo().get_param('web.base.url') + '/r/'
+ res = dict.fromkeys(self.ids, False)
+ for sms in self:
+ if not sms.mailing_id or not sms.body:
+ res[sms.id] = sms.body
+ continue
+
+ body = sms.body
+ for url in re.findall(tools.TEXT_URL_REGEX, body):
+ if url.startswith(shortened_schema):
+ body = body.replace(url, url + '/s/%s' % sms.id)
+ res[sms.id] = body
+ return res
+
+ def _postprocess_iap_sent_sms(self, iap_results, failure_reason=None, delete_all=False):
+ all_sms_ids = [item['res_id'] for item in iap_results]
+ if any(sms.mailing_id for sms in self.env['sms.sms'].sudo().browse(all_sms_ids)):
+ for state in self.IAP_TO_SMS_STATE.keys():
+ sms_ids = [item['res_id'] for item in iap_results if item['state'] == state]
+ traces = self.env['mailing.trace'].sudo().search([
+ ('sms_sms_id_int', 'in', sms_ids)
+ ])
+ if traces and state == 'success':
+ traces.write({'sent': fields.Datetime.now(), 'exception': False})
+ elif traces:
+ traces.set_failed(failure_type=self.IAP_TO_SMS_STATE[state])
+ return super(SmsSms, self)._postprocess_iap_sent_sms(iap_results, failure_reason=failure_reason, delete_all=delete_all)
diff --git a/addons/mass_mailing_sms/models/utm.py b/addons/mass_mailing_sms/models/utm.py
new file mode 100644
index 00000000..67c41648
--- /dev/null
+++ b/addons/mass_mailing_sms/models/utm.py
@@ -0,0 +1,42 @@
+# -*- 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_sms_ids = fields.One2many(
+ 'mailing.mailing', 'campaign_id',
+ domain=[('mailing_type', '=', 'sms')],
+ string='Mass SMS')
+ mailing_sms_count = fields.Integer('Number of Mass SMS', compute="_compute_mailing_sms_count")
+
+ @api.depends('mailing_sms_ids')
+ def _compute_mailing_sms_count(self):
+ for campaign in self:
+ campaign.mailing_sms_count = len(campaign.mailing_sms_ids)
+
+ def action_create_mass_sms(self):
+ action = self.env["ir.actions.actions"]._for_xml_id("mass_mailing.action_create_mass_mailings_from_campaign")
+ action['context'] = {
+ 'default_campaign_id': self.id,
+ 'default_mailing_type': 'sms',
+ 'search_default_assigned_to_me': 1,
+ 'search_default_campaign_id': self.id,
+ 'default_user_id': self.env.user.id,
+ }
+ return action
+
+ def action_redirect_to_mailing_sms(self):
+ action = self.env["ir.actions.actions"]._for_xml_id("mass_mailing_sms.mailing_mailing_action_sms")
+ action['context'] = {
+ 'default_campaign_id': self.id,
+ 'default_mailing_type': 'sms',
+ 'search_default_assigned_to_me': 1,
+ 'search_default_campaign_id': self.id,
+ 'default_user_id': self.env.user.id,
+ }
+ action['domain'] = [('mailing_type', '=', 'sms')]
+ return action