diff options
Diffstat (limited to 'addons/sms/models/mail_thread.py')
| -rw-r--r-- | addons/sms/models/mail_thread.py | 246 |
1 files changed, 210 insertions, 36 deletions
diff --git a/addons/sms/models/mail_thread.py b/addons/sms/models/mail_thread.py index a439487e4e9..fd47be27a5d 100644 --- a/addons/sms/models/mail_thread.py +++ b/addons/sms/models/mail_thread.py @@ -3,9 +3,9 @@ import logging -from odoo import models, _ - -from odoo.addons.iap.models.iap import InsufficientCreditError +from odoo import api, models +from odoo.addons.phone_validation.tools import phone_validation +from odoo.tools import html2plaintext _logger = logging.getLogger(__name__) @@ -13,7 +13,7 @@ _logger = logging.getLogger(__name__) class MailThread(models.AbstractModel): _inherit = 'mail.thread' - def _get_default_sms_recipients(self): + def _sms_get_default_partners(self): """ This method will likely need to be overriden by inherited models. :returns partners: recordset of res.partner """ @@ -24,36 +24,210 @@ class MailThread(models.AbstractModel): partners |= self.mapped('partner_ids') return partners - def message_post_send_sms(self, sms_message, numbers=None, partners=None, note_msg=None, log_error=False): - """ Send an SMS text message and post an internal note in the chatter if successfull - :param sms_message: plaintext message to send by sms - :param partners: the numbers to send to, if none are given it will take those - from partners or _get_default_sms_recipients - :param partners: the recipients partners, if none are given it will take those - from _get_default_sms_recipients, this argument - is ignored if numbers is defined - :param note_msg: message to log in the chatter, if none is given a default one - containing the sms_message is logged + def _sms_get_number_fields(self): + """ This method returns the fields to use to find the number to use to + send an SMS on a record. """ + return ['mobile'] + + def _sms_get_recipients_info(self, force_field=False): + """" Get SMS recipient information on current record set. This method + checks for numbers and sanitation in order to centralize computation. + + Example of use cases + + * click on a field -> number is actually forced from field, find customer + linked to record, force its number to field or fallback on customer fields; + * contact -> find numbers from all possible phone fields on record, find + customer, force its number to found field number or fallback on customer fields; + + :return dict: record.id: { + 'partner': a res.partner recordset that is the customer (void or singleton); + 'sanitized': sanitized number to use (coming from record's field or partner's mobile + or phone). Set to False is number impossible to parse and format; + 'number': original number before sanitation; + } for each record in self + """ + result = dict.fromkeys(self.ids, False) + number_fields = self._sms_get_number_fields() + for record in self: + tocheck_fields = [force_field] if force_field else number_fields + all_numbers = [record[fname] for fname in tocheck_fields if fname in record] + all_partners = record._sms_get_default_partners() + + valid_number = False + for fname in [f for f in tocheck_fields if f in record]: + valid_number = phone_validation.phone_get_sanitized_record_number(record, number_fname=fname) + if valid_number: + break + + if valid_number: + result[record.id] = { + 'partner': all_partners[0] if all_partners else self.env['res.partner'], + 'sanitized': valid_number, 'number': valid_number, + } + elif all_partners: + partner_number, partner = False, self.env['res.partner'] + for partner in all_partners: + partner_number = partner.mobile or partner.phone + if partner_number: + partner_number = phone_validation.phone_sanitize_numbers_string_w_record(partner_number, record)[partner_number]['sanitized'] + if partner_number: + break + + if partner_number: + result[record.id] = {'partner': partner, 'sanitized': partner_number, 'number': partner_number} + else: + result[record.id] = {'partner': partner, 'sanitized': False, 'number': partner.mobile or partner.phone} + elif all_numbers: + result[record.id] = {'partner': self.env['res.partner'], 'sanitized': False, 'number': all_numbers[0]} + else: + result[record.id] = {'partner': self.env['res.partner'], 'sanitized': False, 'number': False} + return result + + def _message_sms(self, body, subtype_id=False, partner_ids=False, number_field=False, + sms_numbers=None, sms_pid_to_number=None, **kwargs): + """ Main method to post a message on a record using SMS-based notification + method. + + :param body: content of SMS; + :param subtype_id: mail.message.subtype used in mail.message associated + to the sms notification process; + :param partner_ids: if set is a record set of partners to notify; + :param number_field: if set is a name of field to use on current record + to compute a number to notify; + :param sms_numbers: see ``_notify_record_by_sms``; + :param sms_pid_to_number: see ``_notify_record_by_sms``; + """ + self.ensure_one() + sms_pid_to_number = sms_pid_to_number if sms_pid_to_number is not None else {} + + if number_field or (partner_ids is False and sms_numbers is None): + info = self._sms_get_recipients_info(force_field=number_field)[self.id] + info_partner_ids = info['partner'].ids if info['partner'] else False + info_number = info['sanitized'] if info['sanitized'] else info['number'] + if info_partner_ids and info_number: + sms_pid_to_number[info_partner_ids[0]] = info_number + if info_partner_ids: + partner_ids = info_partner_ids + (partner_ids or []) + if info_number and not info_partner_ids: + sms_numbers = [info_number] + (sms_numbers or []) + + if subtype_id is False: + subtype_id = self.env['ir.model.data'].xmlid_to_res_id('mail.mt_comment') + + return self.message_post( + body=body, partner_ids=partner_ids or [], # TDE FIXME: temp fix otherwise crash mail_thread.py + message_type='sms', subtype_id=subtype_id, + sms_numbers=sms_numbers, sms_pid_to_number=sms_pid_to_number, + **kwargs + ) + + @api.multi + def _notify_thread(self, message, msg_vals=False, **kwargs): + recipients_data = super(MailThread, self)._notify_thread(message, msg_vals=msg_vals, **kwargs) + self._notify_record_by_sms(message, recipients_data, msg_vals=msg_vals, **kwargs) + return recipients_data + + @api.multi + def _notify_record_by_sms(self, message, recipients_data, msg_vals=False, + sms_numbers=None, sms_pid_to_number=None, + check_existing=False, put_in_queue=False, **kwargs): + """ Notification method: by SMS. + + :param message: mail.message record to notify; + :param recipients_data: see ``_notify_thread``; + :param msg_vals: see ``_notify_thread``; + + :param sms_numbers: additional numbers to notify in addition to partners + and classic recipients; + :param pid_to_number: force a number to notify for a given partner ID + instead of taking its mobile / phone number; + :param check_existing: check for existing notifications to update based on + mailed recipient, otherwise create new notifications; + :param put_in_queue: use cron to send queued SMS instead of sending them + directly; """ - if not numbers: - if not partners: - partners = self._get_default_sms_recipients() - - # Collect numbers, we will consider the message to be sent if at least one number can be found - numbers = list(set([i.mobile for i in partners if i.mobile])) - - if numbers: - try: - self.env['sms.api']._send_sms(numbers, sms_message) - mail_message = note_msg or _('SMS message sent: %s') % sms_message - - except InsufficientCreditError as e: - if not log_error: - raise e - mail_message = _('Insufficient credit, unable to send SMS message: %s') % sms_message - else: - mail_message = _('No mobile number defined, unable to send SMS message: %s') % sms_message - - for thread in self: - thread.message_post(body=mail_message) - return False + sms_pid_to_number = sms_pid_to_number if sms_pid_to_number is not None else {} + sms_numbers = sms_numbers if sms_numbers is not None else [] + sms_create_vals = [] + sms_all = self.env['sms.sms'].sudo() + + # pre-compute SMS data + body = msg_vals['body'] if msg_vals and msg_vals.get('body') else message.body + sms_base_vals = { + 'body': html2plaintext(body).rstrip('\n'), + 'mail_message_id': message.id, + 'state': 'outgoing', + } + + # notify from computed recipients_data (followers, specific recipients) + partners_data = [r for r in recipients_data['partners'] if r['notif'] == 'sms'] + partner_ids = [r['id'] for r in partners_data] + if partner_ids: + for partner in self.env['res.partner'].sudo().browse(partner_ids): + number = sms_pid_to_number.get(partner.id) or partner.mobile or partner.phone + sanitize_res = phone_validation.phone_sanitize_numbers_string_w_record(number, partner)[number] + number = sanitize_res['sanitized'] or number + sms_create_vals.append(dict( + sms_base_vals, + partner_id=partner.id, + number=number + )) + + # notify from additional numbers + if sms_numbers: + sanitized = phone_validation.phone_sanitize_numbers_w_record(sms_numbers, self) + tocreate_numbers = [ + value['sanitized'] or original + for original, value in sanitized.items() + if value['code'] != 'empty' + ] + sms_create_vals += [dict(sms_base_vals, partner_id=False, number=n) for n in tocreate_numbers] + + # create sms and notification + existing_pids, existing_numbers = [], [] + if sms_create_vals: + sms_all |= self.env['sms.sms'].sudo().create(sms_create_vals) + + if check_existing: + existing = self.env['mail.notification'].sudo().search([ + '|', ('res_partner_id', 'in', partner_ids), + '&', ('res_partner_id', '=', False), ('sms_number', 'in', sms_numbers), + ('notification_type', '=', 'sms'), + ('mail_message_id', '=', message.id) + ]) + for n in existing: + if n.res_partner_id.id in partner_ids and n.mail_message_id == message: + existing_pids.append(n.res_partner_id.id) + if not n.res_partner_id and n.sms_number in sms_numbers and n.mail_message_id == message: + existing_numbers.append(n.sms_number) + + notif_create_values = [{ + 'mail_message_id': message.id, + 'res_partner_id': sms.partner_id.id, + 'sms_number': sms.number, + 'notification_type': 'sms', + 'sms_id': sms.id, + 'is_read': True, # discard Inbox notification + 'notification_status': 'ready', + } for sms in sms_all if (sms.partner_id and sms.partner_id.id not in existing_pids) or (not sms.partner_id and sms.number not in existing_numbers)] + if notif_create_values: + self.env['mail.notification'].sudo().create(notif_create_values) + + if existing_pids or existing_numbers: + for sms in sms_all: + notif = next((n for n in existing if + (n.res_partner_id.id in existing_pids and n.res_partner_id.id == sms.partner_id.id) or + (not n.res_partner_id and n.sms_number in existing_numbers and n.sms_number == sms.number)), False) + if notif: + notif.write({ + 'notification_type': 'sms', + 'notification_status': 'ready', + 'sms_id': sms.id, + 'sms_number': sms.number, + }) + + if sms_all and not put_in_queue: + sms_all.send(auto_commit=False, raise_exception=False) + + return True |
