summaryrefslogtreecommitdiff
path: root/addons/sms/models/mail_thread.py
diff options
context:
space:
mode:
Diffstat (limited to 'addons/sms/models/mail_thread.py')
-rw-r--r--addons/sms/models/mail_thread.py246
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