summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThibault Delavallée <tde@odoo.com>2019-07-26 11:43:55 +0000
committerThibault Delavallée <tde@odoo.com>2019-08-12 12:00:22 +0000
commit5d1f08a01289375acbe44c2f33d95869477cdb1e (patch)
tree1996e222ad5a9b711b964e9c36aae4347affd521
parent7e8a02cd6e9f08e5ff45db804b357bc3537ff1f6 (diff)
[IMP] sms: integrate duplicate / blacklist into composer in batch mode
PURPOSE SMS are a powerful marketing tool. For instance it is perfect to announce a sale or to communicate a coupon code, to welcome a new customer in a fidelity program, ... Purpose of this task is to integrate SMS sending in batch in mass mailing. It will use same mailing objects but sending SMS instead of emails. Some metrics and flows will have to be slightly updated at the same time. SPECIFICATIONS Purpose of this commit is to support blacklisted and already-done numbers when sending SMS. * blacklist support: necessary to avoid sending SMS in batch to people that asked to stop being spammed; * already-done support: when performing mailing in batch or doing A/B testing we may have already-done SMS; When sending SMS in batch, already update state and error code of SMS that should not be sent * blacklisted number: set to canceled, with blacklist error code; * duplicated number: set to canceled, with duplicated error code; * invalid number (cannot sanitize): set to error, with either missing number if void or wrong format if not void; Support of blacklist / duplicated / invalid will notably be used when performing SMS marketing (mass SMS) and marketing automation using mass SMS. LINKS Task 1997464 PR #34424 Original SMS addition: Task 1922163 (4287481)
-rw-r--r--addons/sms/models/sms_sms.py5
-rw-r--r--addons/sms/wizard/sms_composer.py54
-rw-r--r--addons/test_mail_full/tests/test_sms_composer.py75
3 files changed, 124 insertions, 10 deletions
diff --git a/addons/sms/models/sms_sms.py b/addons/sms/models/sms_sms.py
index fbc68011439..972c03bd8f7 100644
--- a/addons/sms/models/sms_sms.py
+++ b/addons/sms/models/sms_sms.py
@@ -29,7 +29,10 @@ class SmsSms(models.Model):
('sms_number_missing', 'Missing Number'),
('sms_number_format', 'Wrong Number Format'),
('sms_credit', 'Insufficient Credit'),
- ('sms_server', 'Server Error')
+ ('sms_server', 'Server Error'),
+ # mass mode specific codes
+ ('sms_blacklist', 'Blacklisted'),
+ ('sms_duplicate', 'Duplicate'),
])
def send(self, delete_all=False, auto_commit=False, raise_exception=False):
diff --git a/addons/sms/wizard/sms_composer.py b/addons/sms/wizard/sms_composer.py
index 9da5d94bd9a..d5153575ca7 100644
--- a/addons/sms/wizard/sms_composer.py
+++ b/addons/sms/wizard/sms_composer.py
@@ -55,6 +55,7 @@ class SendSMS(models.TransientModel):
# options for comment and mass mode
mass_keep_log = fields.Boolean('Keep a note on document')
mass_force_send = fields.Boolean('Send directly')
+ mass_use_blacklist = fields.Boolean('Use blacklist', default=True)
# recipients
recipient_description = fields.Text('Recipients (Partners)', compute='_compute_description')
recipient_invalid = fields.Text('Invalid recipients', compute='_compute_description')
@@ -189,7 +190,7 @@ class SendSMS(models.TransientModel):
def _action_send_sms_mass_w_log(self, records=None):
records = records if records is not None else self._get_records()
- all_bodies = self._prepare_body_values(records=records)
+ all_bodies = self._prepare_body_values(records)
subtype_id = self.env['ir.model.data'].xmlid_to_res_id('mail.mt_note')
messages = self.env['mail.message']
@@ -201,13 +202,31 @@ class SendSMS(models.TransientModel):
# Mass mode specific
# ------------------------------------------------------------
- def _prepare_recipient_values(self, records=None):
- records = records if records is not None else self._get_records()
+ def _get_blacklist_record_ids(self, records, recipients_info):
+ """ Get a list of blacklisted records. Those will be directly canceled
+ with the right error code. """
+ if self.mass_use_blacklist:
+ bl_numbers = self.env['phone.blacklist'].sudo().search([]).mapped('number')
+ return [r.id for r in records if recipients_info[r.id]['sanitized'] in bl_numbers]
+ return []
+
+ def _get_done_record_ids(self, records, recipients_info):
+ """ Get a list of already-done records. Order of record set is used to
+ spot duplicates so pay attention to it if necessary. """
+ done_ids, done = [], []
+ for record in records:
+ sanitized = recipients_info[record.id]['sanitized']
+ if sanitized in done:
+ done_ids.append(record.id)
+ else:
+ done.append(sanitized)
+ return done_ids
+
+ def _prepare_recipient_values(self, records):
recipients_info = records._sms_get_recipients_info(force_field=self.number_field_name)
return recipients_info
- def _prepare_body_values(self, records=None):
- records = records if records is not None else self._get_records()
+ def _prepare_body_values(self, records):
if self.template_id and self.body == self.template_id.body:
lang_to_rids = self.template_id._get_ids_per_lang(records.ids)
all_bodies = {}
@@ -220,17 +239,34 @@ class SendSMS(models.TransientModel):
def _prepare_mass_sms_values(self, records=None):
records = records if records is not None else self._get_records()
- all_bodies = self._prepare_body_values(records=records)
- all_recipients = self._prepare_recipient_values(records=records)
+ all_bodies = self._prepare_body_values(records)
+ all_recipients = self._prepare_recipient_values(records)
+ blacklist_ids = self._get_blacklist_record_ids(records, all_recipients)
+ done_ids = self._get_done_record_ids(records, all_recipients)
result = {}
for record in records:
recipients = all_recipients[record.id]
+ sanitized = recipients['sanitized']
+ if sanitized and record.id in blacklist_ids:
+ state = 'canceled'
+ error_code = 'sms_blacklist'
+ elif sanitized and record.id in done_ids:
+ state = 'canceled'
+ error_code = 'sms_duplicate'
+ elif not sanitized:
+ state = 'error'
+ error_code = 'sms_number_format' if recipients['number'] else 'sms_number_missing'
+ else:
+ state = 'outgoing'
+ error_code = ''
+
result[record.id] = {
'body': all_bodies[record.id],
'partner_id': recipients['partner'].id,
- 'number': recipients['sanitized'] or recipients['number'],
- 'state': 'outgoing' if recipients['sanitized'] else 'error',
+ 'number': sanitized if sanitized else recipients['number'],
+ 'state': state,
+ 'error_code': error_code,
}
return result
diff --git a/addons/test_mail_full/tests/test_sms_composer.py b/addons/test_mail_full/tests/test_sms_composer.py
index 19a64e415ad..9801dfaabb5 100644
--- a/addons/test_mail_full/tests/test_sms_composer.py
+++ b/addons/test_mail_full/tests/test_sms_composer.py
@@ -216,6 +216,81 @@ class TestSMSComposerMass(test_mail_full_common.BaseFunctionalTest, sms_common.M
for partner in self.partners:
self.assertSMSOutgoing(partner, None, self._test_body)
+ def test_composer_mass_active_ids_w_blacklist(self):
+ self.env['phone.blacklist'].create([{
+ 'number': p.phone_sanitized,
+ 'active': True,
+ } for p in self.partners[:5]])
+
+ with self.sudo('employee'):
+ composer = self.env['sms.composer'].with_context(
+ default_composition_mode='mass',
+ default_res_model='mail.test.sms',
+ active_ids=self.records.ids,
+ ).create({
+ 'body': self._test_body,
+ 'mass_use_blacklist': True,
+ })
+
+ with self.mockSMSGateway():
+ composer.action_send_sms()
+
+ for partner in self.partners[5:]:
+ self.assertSMSOutgoing(partner, partner.phone_sanitized, content=self._test_body)
+ for partner in self.partners[:5]:
+ self.assertSMSCanceled(partner, partner.phone_sanitized, 'sms_blacklist', content=self._test_body)
+
+ def test_composer_mass_active_ids_wo_blacklist(self):
+ self.env['phone.blacklist'].create([{
+ 'number': p.phone_sanitized,
+ 'active': True,
+ } for p in self.partners[:5]])
+
+ with self.sudo('employee'):
+ composer = self.env['sms.composer'].with_context(
+ default_composition_mode='mass',
+ default_res_model='mail.test.sms',
+ active_ids=self.records.ids,
+ ).create({
+ 'body': self._test_body,
+ 'mass_use_blacklist': False,
+ })
+
+ with self.mockSMSGateway():
+ composer.action_send_sms()
+
+ for partner in self.partners:
+ self.assertSMSOutgoing(partner, partner.phone_sanitized, content=self._test_body)
+
+ def test_composer_mass_active_ids_w_blacklist_and_done(self):
+ self.env['phone.blacklist'].create([{
+ 'number': p.phone_sanitized,
+ 'active': True,
+ } for p in self.partners[:5]])
+ for p in self.partners[8:]:
+ p.mobile = self.partners[8].mobile
+ self.assertEqual(p.phone_sanitized, self.partners[8].phone_sanitized)
+
+ with self.sudo('employee'):
+ composer = self.env['sms.composer'].with_context(
+ default_composition_mode='mass',
+ default_res_model='mail.test.sms',
+ active_ids=self.records.ids,
+ ).create({
+ 'body': self._test_body,
+ 'mass_use_blacklist': True,
+ })
+
+ with self.mockSMSGateway():
+ composer.action_send_sms()
+
+ for partner in self.partners[8:]:
+ self.assertSMSOutgoing(partner, partner.phone_sanitized, content=self._test_body)
+ for partner in self.partners[5:8]:
+ self.assertSMSCanceled(partner, partner.phone_sanitized, 'sms_duplicate', content=self._test_body)
+ for partner in self.partners[:5]:
+ self.assertSMSCanceled(partner, partner.phone_sanitized, 'sms_blacklist', content=self._test_body)
+
def test_composer_mass_active_ids_w_template(self):
with self.sudo('employee'):
composer = self.env['sms.composer'].with_context(