diff --git a/addons/phone_validation/models/phone_validation_mixin.py b/addons/phone_validation/models/phone_validation_mixin.py index f378387fc41..f062586c7c3 100644 --- a/addons/phone_validation/models/phone_validation_mixin.py +++ b/addons/phone_validation/models/phone_validation_mixin.py @@ -28,6 +28,6 @@ class PhoneValidationMixin(models.AbstractModel): number, country.code if country else None, country.phone_code if country else None, - always_international=always_international, + force_format='INTERNATIONAL' if always_international else 'NATIONAL', raise_exception=False ) diff --git a/addons/phone_validation/tools/phone_validation.py b/addons/phone_validation/tools/phone_validation.py index 7ca7bed6ea8..29828ffd739 100644 --- a/addons/phone_validation/tools/phone_validation.py +++ b/addons/phone_validation/tools/phone_validation.py @@ -26,14 +26,21 @@ try: return phone_nbr - def phone_format(number, country_code, country_phone_code, always_international=True, raise_exception=True): + def phone_format(number, country_code, country_phone_code, force_format='INTERNATIONAL', raise_exception=True): """ Format the given phone number according to the localisation and international options. - :param number: number to convert - :param country_code: the ISO country code in two chars - :type country_code: str - :param country_phone_code: country dial in codes, defined by the ITU-T (Ex: 32 for Belgium) - :type country_phone_code: int - :rtype: str + :param number: number to convert + :param country_code: the ISO country code in two chars + :type country_code: str + :param country_phone_code: country dial in codes, defined by the ITU-T (Ex: 32 for Belgium) + :type country_phone_code: int + :param force_format: stringified version of format globals (see + https://github.com/daviddrysdale/python-phonenumbers/blob/dev/python/phonenumbers/phonenumberutil.py) + 'E164' = 0 + 'INTERNATIONAL' = 1 + 'NATIONAL' = 2 + 'RFC3966' = 3 + :type force_format: str + :rtype: str """ try: phone_nbr = phone_parse(number, country_code) @@ -43,7 +50,11 @@ try: else: _logger.warning(_('Unable to format %s:\n%s'), number, e) return number - if always_international or phone_nbr.country_code != country_phone_code: + if force_format == 'E164': + phone_fmt = phonenumbers.PhoneNumberFormat.E164 + elif force_format == 'RFC3966': + phone_fmt = phonenumbers.PhoneNumberFormat.RFC3966 + elif force_format == 'INTERNATIONAL' or phone_nbr.country_code != country_phone_code: phone_fmt = phonenumbers.PhoneNumberFormat.INTERNATIONAL else: phone_fmt = phonenumbers.PhoneNumberFormat.NATIONAL @@ -54,7 +65,7 @@ except ImportError: def phone_parse(number, country_code): return False - def phone_format(number, country_code, country_phone_code, always_international=True, raise_exception=True): + def phone_format(number, country_code, country_phone_code, force_format='INTERNATIONAL', raise_exception=True): global _phonenumbers_lib_warning if not _phonenumbers_lib_warning: _logger.warning( @@ -63,3 +74,56 @@ except ImportError: ) _phonenumbers_lib_warning = True return number + + +def phone_sanitize_numbers(numbers, country_code, country_phone_code, force_format='E164'): + valid, invalid, void_count = [], [], 0 + for number in numbers: + if not number: + void_count += 1 + continue + try: + sanitized = phone_format( + number, country_code, country_phone_code, + force_format=force_format, raise_exception=True) + except Exception as e: + invalid.append(number) + else: + valid.append(sanitized) + return valid, invalid, void_count + + +def phone_sanitize_numbers_w_record(numbers, country_code, country_phone_code, record, record_country_fname='country_id', force_format='E164'): + if not country_code or not country_phone_code: + country = False + if record and record_country_fname in record and record[record_country_fname]: + country = record[record_country_fname] + elif record: + country = record.env.company.country_id + if country: + country_code = country_code if country_code else country.code + country_phone_code = country_phone_code if country_phone_code else country.phone_code + return phone_sanitize_numbers(numbers, country_code, country_phone_code, force_format=force_format) + + +def phone_sanitize_numbers_string_w_record(numbers_str, country_code, country_phone_code, record, record_country_fname='country_id', force_format='E164'): + found_numbers = [number.strip() for number in numbers_str.split(',')] + return phone_sanitize_numbers_w_record(found_numbers, country_code, country_phone_code, record, record_country_fname, force_format=force_format) + + +def phone_get_sanitized_records_number(records, number_fname='mobile', country_fname='country_id', force_format='E164'): + res = dict.fromkeys(records.ids, False) + for record in records: + number = record[number_fname] + valid, invalid, void_count = phone_sanitize_numbers_w_record([number], None, None, records, country_fname,force_format=force_format) + if valid: + res[record.id] = valid[0] + elif void_count: + res[record.id] = False + else: + res[record.id] = False + return res + + +def phone_get_sanitized_record_number(record, number_fname='mobile', country_fname='country_id', force_format='E164'): + return phone_get_sanitized_records_number(record, number_fname, country_fname, force_format=force_format)[record.id] diff --git a/addons/sms/__manifest__.py b/addons/sms/__manifest__.py index acc027d90d4..4ef5721be8f 100644 --- a/addons/sms/__manifest__.py +++ b/addons/sms/__manifest__.py @@ -10,7 +10,7 @@ This module gives a framework for SMS text messaging The service is provided by the In App Purchase Odoo platform. """, - 'depends': ['base', 'iap', 'mail'], + 'depends': ['base', 'iap', 'mail', 'phone_validation'], 'data': [ 'wizard/sms_composer_views.xml', 'views/res_config_settings_views.xml', diff --git a/addons/sms/models/sms_api.py b/addons/sms/models/sms_api.py index f770676d0ca..1b6be2607fc 100644 --- a/addons/sms/models/sms_api.py +++ b/addons/sms/models/sms_api.py @@ -1,8 +1,7 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from odoo import api, fields, models -from odoo.exceptions import UserError +from odoo import api, models from odoo.addons.iap.models import iap DEFAULT_ENDPOINT = 'https://iap-sms.odoo.com' @@ -13,15 +12,47 @@ class SmsApi(models.AbstractModel): _description = 'SMS API' @api.model - def _send_sms(self, numbers, message): - """ Send sms - """ + def _contact_iap(self, local_endpoint, params): account = self.env['iap.account'].get('sms') + params['account_token'] = account.account_token + endpoint = self.env['ir.config_parameter'].sudo().get_param('sms.endpoint', DEFAULT_ENDPOINT) + # TODO PRO, the default timeout is 15, do we have to increase it ? + return iap.jsonrpc(endpoint + local_endpoint, params=params) + + @api.model + def _send_sms(self, numbers, message): + """ Send a single message to several numbers + + :param numbers: list of E164 formatted phone numbers + :param message: content to send + + :raises ? TDE FIXME + """ params = { - 'account_token': account.account_token, 'numbers': numbers, 'message': message, } - endpoint = self.env['ir.config_parameter'].sudo().get_param('sms.endpoint', DEFAULT_ENDPOINT) - r = iap.jsonrpc(endpoint + '/iap/message_send', params=params) - return True + return self._contact_iap('/iap/message_send', params) + + @api.model + def _send_sms_batch(self, messages): + """ Send SMS using IAP in batch mode + + :param messages: list of SMS to send, structured as dict [{ + 'res_id': integer: ID of sms.sms, + 'number': string: E164 formatted phone number, + 'content': string: content to send + }] + + :return: return of /iap/sms/1/send controller which is a list of dict [{ + 'res_id': integer: ID of sms.sms, + 'state': string: 'insufficient_credit' or 'wrong_format_number' or 'success', + 'credit': integer: number of credits spent to send this SMS, + }] + + :raises: normally none + """ + params = { + 'messages': messages + } + return self._contact_iap('/iap/sms/1/send', params)