diff --git a/addons/calendar_sms/__init__.py b/addons/calendar_sms/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..dc5e6b693d19dcacd224b7ab27b26f75e66cb7b2 --- /dev/null +++ b/addons/calendar_sms/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import models diff --git a/addons/calendar_sms/__manifest__.py b/addons/calendar_sms/__manifest__.py new file mode 100644 index 0000000000000000000000000000000000000000..0e734074aaa4ec9a84b5781b6950d7f375769951 --- /dev/null +++ b/addons/calendar_sms/__manifest__.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +{ + 'name': "Calendar SMS", + 'summary': 'Send text messages as event reminders', + 'description': "Send text messages as event reminders", + 'category': 'Hidden', + 'version': '1.0', + 'depends': ['calendar', 'sms'], + 'data': [ + 'views/calendar_views.xml', + ], + 'application': False, + 'auto_install': True, +} diff --git a/addons/calendar_sms/i18n/calendar_sms.pot b/addons/calendar_sms/i18n/calendar_sms.pot new file mode 100644 index 0000000000000000000000000000000000000000..073430308ff663532d665214dad14c40f05f1f7e --- /dev/null +++ b/addons/calendar_sms/i18n/calendar_sms.pot @@ -0,0 +1,49 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * calendar_sms +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.saas~18+e\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-09-20 11:33+0000\n" +"PO-Revision-Date: 2017-09-20 11:33+0000\n" +"Last-Translator: <>\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: calendar_sms +#: model:ir.model,name:calendar_sms.model_calendar_event +msgid "Event" +msgstr "" + +#. module: calendar_sms +#: model:ir.model,name:calendar_sms.model_calendar_alarm +msgid "Event alarm" +msgstr "" + +#. module: calendar_sms +#: code:addons/calendar_sms/models/calendar.py:23 +#, python-format +msgid "Event reminder: %s on %s." +msgstr "" + +#. module: calendar_sms +#: code:addons/calendar_sms/models/calendar.py:24 +#, python-format +msgid "SMS text message reminder sent !" +msgstr "" + +#. module: calendar_sms +#: model:ir.actions.act_window,name:calendar_sms.sms_message_send_action_mutli +msgid "Send SMS to attendees" +msgstr "" + +#. module: calendar_sms +#: model:ir.model,name:calendar_sms.model_calendar_alarm_manager +msgid "calendar.alarm_manager" +msgstr "" + diff --git a/addons/calendar_sms/models/__init__.py b/addons/calendar_sms/models/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..a71750ab34d296f5354481d53b6c2f566dbd74e5 --- /dev/null +++ b/addons/calendar_sms/models/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import calendar diff --git a/addons/calendar_sms/models/calendar.py b/addons/calendar_sms/models/calendar.py new file mode 100644 index 0000000000000000000000000000000000000000..d22200b9363235ee5cb98db1bb605de745d73de1 --- /dev/null +++ b/addons/calendar_sms/models/calendar.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import logging + +from odoo import api, fields, models, _ + +_logger = logging.getLogger(__name__) + + +class CalendarEvent(models.Model): + _inherit = 'calendar.event' + + def _get_default_sms_recipients(self): + """ Method overriden from mail.thread (defined in the sms module). + SMS text messages will be sent to attendees that haven't declined the event(s). + """ + return self.mapped('attendee_ids').filtered(lambda att: att.state != 'declined').mapped('partner_id') + + def _do_sms_reminder(self): + """ Send an SMS text reminder to attendees that haven't declined the event """ + for event in self: + sms_msg = _("Event reminder: %s on %s.") % (event.name, event.start_datetime or event.start_date) + note_msg = _('SMS text message reminder sent !') + event.message_post_send_sms(sms_msg, note_msg=note_msg) + + +class CalendarAlarm(models.Model): + _inherit = 'calendar.alarm' + + type = fields.Selection(selection_add=[('sms', 'SMS Text Message')]) + + +class AlarmManager(models.AbstractModel): + _inherit = 'calendar.alarm_manager' + + @api.model + def get_next_mail(self): + """ Cron method, overriden here to send SMS reminders as well + """ + result = super(AlarmManager, self).get_next_mail() + now = fields.Datetime.now() + last_sms_cron = self.env['ir.config_parameter'].get_param('calendar_sms.last_sms_cron', default=now) + cron = self.env['ir.model.data'].get_object('calendar', 'ir_cron_scheduler_alarm') + + interval_to_second = { + "weeks": 7 * 24 * 60 * 60, + "days": 24 * 60 * 60, + "hours": 60 * 60, + "minutes": 60, + "seconds": 1 + } + + cron_interval = cron.interval_number * interval_to_second[cron.interval_type] + events_data = self.get_next_potential_limit_alarm('sms', seconds=cron_interval) + + for event in self.env['calendar.event'].browse(events_data): + max_delta = events_data[event.id]['max_duration'] + + if event.recurrency: + found = False + for event_start in event._get_recurrent_date_by_event(): + event_start = event_start.replace(tzinfo=None) + last_found = self.do_check_alarm_for_one_date(event_start, event, max_delta, 0, 'sms', after=last_sms_cron, missing=True) + for alert in last_found: + event.browse(alert['event_id'])._do_sms_reminder() + found = True + if found and not last_found: # if the precedent event had an alarm but not this one, we can stop the search for this event + break + else: + event_start = fields.Datetime.from_string(event.start) + for alert in self.do_check_alarm_for_one_date(event_start, event, max_delta, 0, 'sms', after=last_sms_cron, missing=True): + event.browse(alert['event_id'])._do_sms_reminder() + self.env['ir.config_parameter'].set_param('calendar_sms.last_sms_cron', now) + return result diff --git a/addons/calendar_sms/views/calendar_views.xml b/addons/calendar_sms/views/calendar_views.xml new file mode 100644 index 0000000000000000000000000000000000000000..4682a7098bfd202b11936a1ba4706ca899bb21bf --- /dev/null +++ b/addons/calendar_sms/views/calendar_views.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<odoo> + + <!-- Add action entry in the Action Menu for Events --> + <act_window id="sms_message_send_action_mutli" + name="Send SMS to attendees" + src_model="calendar.event" + res_model="sms.send_sms" + view_type="form" + view_mode="form" + key2="client_action_multi" + target="new"/> + +</odoo> diff --git a/addons/sms/__init__.py b/addons/sms/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..2ae6446f9dc25fe7563ce0c6a4f1cc4904124cc5 --- /dev/null +++ b/addons/sms/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import models +from . import wizard diff --git a/addons/sms/__manifest__.py b/addons/sms/__manifest__.py new file mode 100644 index 0000000000000000000000000000000000000000..18330c1d7936d83ca89f8b7339f7199dbe41b31e --- /dev/null +++ b/addons/sms/__manifest__.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. +{ + 'name': 'SMS gateway', + 'category': 'Tools', + 'summary': 'SMS Text Messaging', + 'description': """ +This module gives a framework for SMS text messaging +---------------------------------------------------- + +The service is provided by the In App Purchase Odoo platform. +""", + 'depends': ['base', 'iap', 'mail'], + 'data': [ + 'wizard/send_sms_views.xml', + 'views/res_partner_views.xml', + ], + 'installable': True, + 'auto_install': True, +} diff --git a/addons/sms/models/__init__.py b/addons/sms/models/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..7a75a7a5f6d8db50e84f7937b9baed3ec70b42c6 --- /dev/null +++ b/addons/sms/models/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import mail_thread +from . import res_partner +from . import sms_api diff --git a/addons/sms/models/mail_thread.py b/addons/sms/models/mail_thread.py new file mode 100644 index 0000000000000000000000000000000000000000..1fb62a72de4b36a71aca4e3df3142f660439fd22 --- /dev/null +++ b/addons/sms/models/mail_thread.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import logging + +from odoo import models, _ + +from odoo.addons.iap.models.iap import InsufficientCreditError + +_logger = logging.getLogger(__name__) + + +class MailThread(models.AbstractModel): + _inherit = 'mail.thread' + + def _get_default_sms_recipients(self): + """ This method will likely need to be overriden by inherited models. + :returns partners: recordset of res.partner + """ + partners = self.env['res.partner'] + if hasattr(self, 'partner_id'): + partners |= self.mapped('partner_id') + if hasattr(self, 'partner_ids'): + 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 + """ + 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 diff --git a/addons/sms/models/res_partner.py b/addons/sms/models/res_partner.py new file mode 100644 index 0000000000000000000000000000000000000000..6d85ef2fe06453aeb1fa930227a838f728ed9d8c --- /dev/null +++ b/addons/sms/models/res_partner.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import models + + +class ResPartner(models.Model): + _inherit = 'res.partner' + + def _get_default_sms_recipients(self): + """ Override of mail.thread method. + SMS recipients on partners are the partners themselves. + """ + return self diff --git a/addons/sms/models/sms_api.py b/addons/sms/models/sms_api.py new file mode 100644 index 0000000000000000000000000000000000000000..1498ce54d98af9bc69a58ac4a7f676290762629c --- /dev/null +++ b/addons/sms/models/sms_api.py @@ -0,0 +1,26 @@ +# -*- 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.addons.iap.models import iap + +DEFAULT_ENDPOINT = 'https://iap-sms.odoo.com' + + +class SmsApi(models.AbstractModel): + _name = 'sms.api' + + @api.model + def _send_sms(self, numbers, message): + """ Send sms + """ + account = self.env['iap.account'].get('sms') + 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 diff --git a/addons/sms/views/res_partner_views.xml b/addons/sms/views/res_partner_views.xml new file mode 100644 index 0000000000000000000000000000000000000000..a35d5f10516512a64f19152e005ee8e2700cd09b --- /dev/null +++ b/addons/sms/views/res_partner_views.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="utf-8"?> +<odoo> + + <!-- Add action entry in the Action Menu for Partners --> + <record id="partner_form_send_sms_form_view" model="ir.ui.view"> + <field name="name">res.partner.form.send.sms</field> + <field name="model">res.partner</field> + <field name="inherit_id" ref="base.view_partner_form"/> + <field name="priority">10</field> + <field name="arch" type="xml"> + <xpath expr="//field[@name='phone']" position="replace"> + <label for="phone"/> + <div class="o_row"> + <field name="phone" widget="phone"/> + <button + type="action" + name="%(sms.send_sms_form_action)d" + class="btn-xs btn-link mb4 fa fa-envelope-o" + attrs="{'invisible':[('phone', '=', False)]}" + context="{'field_name': 'phone'}" + /> + </div> + </xpath> + <xpath expr="//field[@name='mobile']" position="replace"> + <label for="mobile"/> + <div class="o_row"> + <field name="mobile" widget="phone"/> + <button + type="action" + name="%(sms.send_sms_form_action)d" + class="btn-xs btn-link mb4 fa fa-envelope-o" + attrs="{'invisible':[('mobile', '=', False)]}" + context="{'field_name': 'mobile'}" + /> + </div> + </xpath> + </field> + </record> + +</odoo> diff --git a/addons/sms/wizard/__init__.py b/addons/sms/wizard/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..937147a0b4358d89587a181a4ee4b08729238292 --- /dev/null +++ b/addons/sms/wizard/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import send_sms diff --git a/addons/sms/wizard/send_sms.py b/addons/sms/wizard/send_sms.py new file mode 100644 index 0000000000000000000000000000000000000000..25fa0828934ea009878b38502a2216568738b2f0 --- /dev/null +++ b/addons/sms/wizard/send_sms.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. +import logging + + +from odoo import api, fields, models +from odoo.exceptions import UserError +from odoo.addons.iap.models import iap + +_logger = logging.getLogger(__name__) + + +class SendSMS(models.TransientModel): + _name = 'sms.send_sms' + + recipients = fields.Char('Recipients', required=True) + message = fields.Text('Message', required=True) + + def _get_records(self, model): + if self.env.context.get('active_domain'): + records = model.search(self.env.context.get('active_domain')) + elif self.env.context.get('active_ids'): + records = model.browse(self.env.context.get('active_ids', [])) + else: + records = model.browse(self.env.context.get('active_id', [])) + return records + + @api.model + def default_get(self, fields): + result = super(SendSMS, self).default_get(fields) + + active_model = self.env.context.get('active_model') + model = self.env[active_model] + + records = self._get_records(model) + if getattr(records, '_get_default_sms_recipients'): + partners = records._get_default_sms_recipients() + phone_numbers = [] + no_phone_partners = [] + for partner in records: + number = partner.mobile + if number: + phone_numbers.append(number) + else: + no_phone_partners.append(partner.name) + if len(partners) > 1: + if no_phone_partners: + raise UserError(_('Missing mobile number for %s.') % ', '.join(no_phone_partners)) + result['recipients'] = ', '.join(phone_numbers) + return result + + def action_send_sms(self): + numbers = self.recipients.split(',') + + active_model = self.env.context.get('active_model') + model = self.env[active_model] + records = self._get_records(model) + if getattr(records, 'message_post_send_sms'): + records.message_post_send_sms(self.message, numbers=numbers) + else: + self.env['sms.api']._send_sms(numbers, self.message) + return True diff --git a/addons/sms/wizard/send_sms_views.xml b/addons/sms/wizard/send_sms_views.xml new file mode 100644 index 0000000000000000000000000000000000000000..34746c32825ce9b23806834d5878f6ec31d120c8 --- /dev/null +++ b/addons/sms/wizard/send_sms_views.xml @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="utf-8"?> +<odoo> + + <record id="send_sms_view_form" model="ir.ui.view"> + <field name="name">sms_send_sms.form</field> + <field name="model">sms.send_sms</field> + <field name="arch" type="xml"> + <form string="Send an SMS"> + <sheet> + <group> + <field name="recipients"/> + <field name="message" widget="text"/> + </group> + </sheet> + <footer> + <group> + <span> + <button string="Send" type="object" class="oe_highlight" name="action_send_sms"/> + <button string="Cancel" class="oe_link" special="cancel" /> + </span> + </group> + </footer> + </form> + </field> + </record> + <record id="send_sms_form_action" model="ir.actions.act_window"> + <field name="name">Send SMS</field> + <field name="res_model">sms.send_sms</field> + <field name="view_type">form</field> + <field name="view_mode">form</field> + <field name="target">new</field> + </record> + + <!-- Add action entry in the Action Menu for Partners --> + <act_window id="send_sms_action" + name="Send SMS" + src_model="res.partner" + res_model="sms.send_sms" + view_type="form" + view_mode="form" + key2="client_action_multi" + target="new"/> + +</odoo>