diff options
| author | Florimond Husquinet (fhu) <fhu@odoo.com> | 2019-11-15 13:21:02 +0000 |
|---|---|---|
| committer | Thibault Delavallée <tde@odoo.com> | 2020-03-05 11:27:01 +0000 |
| commit | f685e70616e8fe357690cd8e6cfa4543c1d0df24 (patch) | |
| tree | 6b818d3e3ae89d0ba9de2530d24296145883a3ca | |
| parent | e36661ecd9fc455222f4d274eb65ab763a6ccd18 (diff) | |
[IMP] sms: allow to edit number when sending an SMS to one recipient
After this commit
* the SMS button is visible next to a phone number in mass_mailing, and
mass_mailing_sms
* when sending an sms to a single contact, its phone number is now an
editable field in the wizard and there is a warning if the number is
invalid;
* if operator updated the recipient number, update record number according
to the new number only in single recipient mode (aka, solving number
issues directly from interface);
LINKS
Task ID 2088303
Community PR odoo/odoo#40482
Upgrade PR odoo/upgrade#873
Co-Authored-By: Florimond Husquinet <fhu@odoo.com>
Co-Authored-By: Thibault Delavallée <tde@odoo.com>
| -rw-r--r-- | addons/sms/models/mail_thread.py | 5 | ||||
| -rw-r--r-- | addons/sms/wizard/sms_composer.py | 92 | ||||
| -rw-r--r-- | addons/sms/wizard/sms_composer_views.xml | 23 | ||||
| -rw-r--r-- | addons/test_mail_full/tests/test_sms_composer.py | 22 |
4 files changed, 110 insertions, 32 deletions
diff --git a/addons/sms/models/mail_thread.py b/addons/sms/models/mail_thread.py index e1dac1fba33..86713fddc24 100644 --- a/addons/sms/models/mail_thread.py +++ b/addons/sms/models/mail_thread.py @@ -128,11 +128,8 @@ class MailThread(models.AbstractModel): 'partner_store': True, 'field_store': fname, } - elif all_numbers: - result[record.id] = {'partner': self.env['res.partner'], 'sanitized': False, 'number': all_numbers[0]} else: - idx, value = next(((idx, val) for idx, val in enumerate(all_numbers)), (0, False)) - fname = tocheck_fields[idx] if tocheck_fields else False + value, fname = next(((value, fname) for value, fname in zip(all_numbers, tocheck_fields) if value), (0, False)) result[record.id] = { 'partner': self.env['res.partner'], 'sanitized': False, diff --git a/addons/sms/wizard/sms_composer.py b/addons/sms/wizard/sms_composer.py index 33a9f2a6293..df76d2e9c60 100644 --- a/addons/sms/wizard/sms_composer.py +++ b/addons/sms/wizard/sms_composer.py @@ -72,10 +72,16 @@ class SendSMS(models.TransientModel): mass_force_send = fields.Boolean('Send directly', default=False) mass_use_blacklist = fields.Boolean('Use blacklist', default=True) # recipients - recipient_single_description = fields.Text('Recipients (Partners)', compute='_compute_recipients', compute_sudo=False) recipient_valid_count = fields.Integer('# Valid recipients', compute='_compute_recipients', compute_sudo=False) recipient_invalid_count = fields.Integer('# Invalid recipients', compute='_compute_recipients', compute_sudo=False) - number_field_name = fields.Char(string='Field holding number') + recipient_single_description = fields.Text('Recipients (Partners)', compute='_compute_recipient_single', compute_sudo=False) + recipient_single_number = fields.Char('Stored Recipient Number', compute='_compute_recipient_single', compute_sudo=False) + recipient_single_number_itf = fields.Char( + 'Recipient Number', compute='_compute_recipient_single', + readonly=False, compute_sudo=False, store=True, + help='UX field allowing to edit the recipient number. If changed it will be stored onto the recipient.') + recipient_single_valid = fields.Boolean("Is valid", compute='_compute_recipient_single_valid', compute_sudo=False) + number_field_name = fields.Char('Number Field') numbers = fields.Char('Recipients (Numbers)') sanitized_numbers = fields.Char('Sanitized Number', compute='_compute_sanitized_numbers', compute_sudo=False) # content @@ -99,28 +105,50 @@ class SendSMS(models.TransientModel): @api.depends('res_model', 'res_id', 'res_ids', 'use_active_domain', 'composition_mode', 'number_field_name', 'sanitized_numbers') def _compute_recipients(self): for composer in self: - composer.recipient_single_description = False composer.recipient_valid_count = 0 composer.recipient_invalid_count = 0 - if composer.composition_mode in ('comment', 'mass') and composer.res_model: - records = composer._get_records() + if composer.composition_mode not in ('comment', 'mass') or not composer.res_model: + continue - if records and issubclass(type(records), self.pool['mail.thread']): - res = records._sms_get_recipients_info(force_field=composer.number_field_name) - valid_ids = [rid for rid, rvalues in res.items() if rvalues['sanitized']] - invalid_ids = [rid for rid, rvalues in res.items() if not rvalues['sanitized']] - composer.recipient_valid_count = len(valid_ids) - composer.recipient_invalid_count = len(invalid_ids) - if len(records) == 1: - composer.recipient_single_description = '%s (%s)' % ( - res[records.id]['partner'].name or records.display_name, - res[records.id]['sanitized'] or _("Invalid number") - ) - else: - composer.recipient_invalid_count = 0 if ( - composer.sanitized_numbers or (composer.composition_mode == 'mass' and composer.use_active_domain) - ) else 1 + records = composer._get_records() + if records and issubclass(type(records), self.pool['mail.thread']): + res = records._sms_get_recipients_info(force_field=composer.number_field_name, partner_fallback=not composer.comment_single_recipient) + composer.recipient_valid_count = len([rid for rid, rvalues in res.items() if rvalues['sanitized']]) + composer.recipient_invalid_count = len([rid for rid, rvalues in res.items() if not rvalues['sanitized']]) + else: + composer.recipient_invalid_count = 0 if ( + composer.sanitized_numbers or (composer.composition_mode == 'mass' and composer.use_active_domain) + ) else 1 + + @api.depends('res_model', 'number_field_name') + def _compute_recipient_single(self): + for composer in self: + records = composer._get_records() + if not records or not issubclass(type(records), self.pool['mail.thread']) or not composer.comment_single_recipient: + composer.recipient_single_description = False + composer.recipient_single_number = '' + composer.recipient_single_number_itf = '' + continue + records.ensure_one() + res = records._sms_get_recipients_info(force_field=composer.number_field_name, partner_fallback=False) + composer.recipient_single_description = res[records.id]['partner'].name or records.display_name + composer.recipient_single_number = res[records.id]['number'] or '' + if not composer.recipient_single_number_itf: + composer.recipient_single_number_itf = res[records.id]['number'] or '' + if not composer.number_field_name: + composer.number_field_name = res[records.id]['field_store'] + + @api.depends('recipient_single_number', 'recipient_single_number_itf') + def _compute_recipient_single_valid(self): + for composer in self: + value = composer.recipient_single_number_itf or composer.recipient_single_number + if value: + records = composer._get_records() + sanitized = phone_validation.phone_sanitize_numbers_w_record([value], records)[value]['sanitized'] + composer.recipient_single_valid = bool(sanitized) + else: + composer.recipient_single_valid = False @api.depends('numbers', 'res_model', 'res_id') def _compute_sanitized_numbers(self): @@ -149,8 +177,11 @@ class SendSMS(models.TransientModel): # ------------------------------------------------------------ def action_send_sms(self): - if self.composition_mode in ('numbers', 'comment') and self.recipient_invalid_count: - raise UserError(_('%s invalid recipients') % self.recipient_invalid_count) + if self.composition_mode in ('numbers', 'comment'): + if self.comment_single_recipient and not self.recipient_single_valid: + raise UserError(_('Invalid recipient number. Please update it.')) + elif not self.comment_single_recipient and self.recipient_invalid_count: + raise UserError(_('%s invalid recipients') % self.recipient_invalid_count) self._action_send_sms() return False @@ -164,9 +195,12 @@ class SendSMS(models.TransientModel): if self.composition_mode == 'numbers': return self._action_send_sms_numbers() elif self.composition_mode == 'comment': - if records is not None and issubclass(type(records), self.pool['mail.thread']): + if records is None or not issubclass(type(records), self.pool['mail.thread']): + return self._action_send_sms_numbers() + if self.comment_single_recipient: + return self._action_send_sms_comment_single(records) + else: return self._action_send_sms_comment(records) - return self._action_send_sms_numbers() else: return self._action_send_sms_mass(records) @@ -178,6 +212,16 @@ class SendSMS(models.TransientModel): } for number in self.sanitized_numbers.split(',')]) return True + def _action_send_sms_comment_single(self, records=None): + # If we have a recipient_single_original number, it's possible this number has been corrected in the popup + # if invalid. As a consequence, the test cannot be based on recipient_invalid_count, which count is based + # on the numbers in the database. + records = records if records is not None else self._get_records() + records.ensure_one() + if self.recipient_single_number_itf and self.recipient_single_number_itf != self.recipient_single_number: + records.write({self.number_field_name: self.recipient_single_number_itf}) + return self._action_send_sms_comment(records=records) + def _action_send_sms_comment(self, records=None): records = records if records is not None else self._get_records() subtype_id = self.env['ir.model.data'].xmlid_to_res_id('mail.mt_note') diff --git a/addons/sms/wizard/sms_composer_views.xml b/addons/sms/wizard/sms_composer_views.xml index 181b54aa736..543b731f360 100644 --- a/addons/sms/wizard/sms_composer_views.xml +++ b/addons/sms/wizard/sms_composer_views.xml @@ -14,10 +14,18 @@ <field name="active_domain" invisible="1"/> <field name="res_model" invisible="1"/> <field name="mass_force_send" invisible="1"/> + <field name="recipient_single_valid" invisible="1"/> + <field name="recipient_single_number" invisible="1"/> <field name="number_field_name" invisible="1"/> <field name="numbers" invisible="1"/> <field name="sanitized_numbers" invisible="1"/> + <!-- Single mode information (invalid number) --> + <div colspan="2" class="alert alert-danger text-center mb-3" role="alert" + attrs="{'invisible': ['|', ('comment_single_recipient', '=', False), ('recipient_single_valid', '=', True)]}"> + <p class="my-0">Invalid phone number</p> + </div> + <!-- Mass mode information (res_ids versus active domain) --> <div colspan="2" class="alert alert-info text-center mb-3" role="alert" attrs="{'invisible': [('comment_single_recipient', '=', True)]}"> @@ -31,16 +39,25 @@ and <field class="oe_inline font-weight-bold" name="recipient_invalid_count"/> are invalid. </p> </div> - <field name="recipient_single_description" string="Recipients" - readonly="1" + + <label for="recipient_single_description" string="Recipient" + class="font-weight-bold" attrs="{'invisible': [('comment_single_recipient', '=', False)]}"/> + <div attrs="{'invisible': [('comment_single_recipient', '=', False)]}"> + <field name="recipient_single_description" class="oe_inline"/> + <field name="recipient_single_number_itf" class="oe_inline" nolabel="1"/> + </div> + <field name="body" widget="sms_widget"/> <field name="mass_keep_log" invisible="1"/> </group> </sheet> <footer> + <!-- attrs doesn't work for 'disabled'--> + <button string="Send SMS" type="object" class="oe_highlight" name="action_send_sms" + attrs="{'invisible': ['|',('composition_mode', 'not in', ('comment', 'numbers')),('recipient_single_valid', '=', False)]}"/> <button string="Send SMS" type="object" class="oe_highlight" name="action_send_sms" - attrs="{'invisible': [('composition_mode', 'not in', ('comment', 'numbers'))]}"/> + attrs="{'invisible': ['|',('composition_mode', 'not in', ('comment', 'numbers')),('recipient_single_valid', '=', True)]}" disabled='1'/> <button string="Put in queue" type="object" class="oe_highlight" name="action_send_sms" attrs="{'invisible': [('composition_mode', '!=', 'mass')]}"/> <button string="Send Now" type="object" name="action_send_sms_mass_now" diff --git a/addons/test_mail_full/tests/test_sms_composer.py b/addons/test_mail_full/tests/test_sms_composer.py index 0dbe6636b99..4fc596c1985 100644 --- a/addons/test_mail_full/tests/test_sms_composer.py +++ b/addons/test_mail_full/tests/test_sms_composer.py @@ -131,10 +131,30 @@ class TestSMSComposerComment(test_mail_full_common.TestSMSCommon, test_mail_full self.assertEqual(composer.res_id, self.test_record.id) self.assertEqual(composer.number_field_name, 'phone_nbr') self.assertTrue(composer.comment_single_recipient) - self.assertEqual(composer.recipient_single_description, '%s (%s)' % (self.test_record.customer_id.display_name, self.test_numbers_san[1])) + self.assertEqual(composer.recipient_single_description, self.test_record.customer_id.display_name) + self.assertEqual(composer.recipient_single_number, self.test_numbers[1]) + self.assertEqual(composer.recipient_single_number_itf, self.test_numbers[1]) + self.assertTrue(composer.recipient_single_valid) self.assertEqual(composer.recipient_valid_count, 1) self.assertEqual(composer.recipient_invalid_count, 0) + with self.with_user('employee'): + composer.update({'recipient_single_number_itf': '0123456789'}) + + self.assertFalse(composer.recipient_single_valid) + + with self.with_user('employee'): + composer.update({'recipient_single_number_itf': self.random_numbers[0]}) + + self.assertTrue(composer.recipient_single_valid) + + with self.with_user('employee'): + with self.mockSMSGateway(): + composer.action_send_sms() + + self.test_record.flush() + self.assertEqual(self.test_record.phone_nbr, self.random_numbers[0]) + def test_composer_numbers_no_model(self): with self.with_user('employee'): composer = self.env['sms.composer'].with_context( |
