diff options
| author | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
|---|---|---|
| committer | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
| commit | 3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch) | |
| tree | a44932296ef4a9b71d5f010906253d8c53727726 /addons/sms/static/src | |
| parent | 0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff) | |
initial commit 2
Diffstat (limited to 'addons/sms/static/src')
13 files changed, 990 insertions, 0 deletions
diff --git a/addons/sms/static/src/bugfix/bugfix.js b/addons/sms/static/src/bugfix/bugfix.js new file mode 100644 index 00000000..ee604b8b --- /dev/null +++ b/addons/sms/static/src/bugfix/bugfix.js @@ -0,0 +1,10 @@ +/** + * This file allows introducing new JS modules without contaminating other files. + * This is useful when bug fixing requires adding such JS modules in stable + * versions of Odoo. Any module that is defined in this file should be isolated + * in its own file in master. + */ +odoo.define('sms/static/src/bugfix/bugfix.js', function (require) { +'use strict'; + +}); diff --git a/addons/sms/static/src/bugfix/bugfix.scss b/addons/sms/static/src/bugfix/bugfix.scss new file mode 100644 index 00000000..c4272e52 --- /dev/null +++ b/addons/sms/static/src/bugfix/bugfix.scss @@ -0,0 +1,6 @@ +/** +* This file allows introducing new styles without contaminating other files. +* This is useful when bug fixing requires adding new components for instance in +* stable versions of Odoo. Any style that is defined in this file should be isolated +* in its own file in master. +*/ diff --git a/addons/sms/static/src/bugfix/bugfix.xml b/addons/sms/static/src/bugfix/bugfix.xml new file mode 100644 index 00000000..c17906f7 --- /dev/null +++ b/addons/sms/static/src/bugfix/bugfix.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="UTF-8"?> +<templates xml:space="preserve"> + +<!-- + This file allows introducing new static templates without contaminating other files. + This is useful when bug fixing requires adding new components for instance in stable + versions of Odoo. Any template that is defined in this file should be isolated + in its own file in master. +--> + +</templates> diff --git a/addons/sms/static/src/bugfix/bugfix_tests.js b/addons/sms/static/src/bugfix/bugfix_tests.js new file mode 100644 index 00000000..938426c6 --- /dev/null +++ b/addons/sms/static/src/bugfix/bugfix_tests.js @@ -0,0 +1,18 @@ +odoo.define('sms/static/src/bugfix/bugfix_tests.js', function (require) { +'use strict'; + +/** + * This file allows introducing new QUnit test modules without contaminating + * other test files. This is useful when bug fixing requires adding new + * components for instance in stable versions of Odoo. Any test that is defined + * in this file should be isolated in its own file in master. + */ +QUnit.module('sms', {}, function () { +QUnit.module('bugfix', {}, function () { +QUnit.module('bugfix_tests.js', { + +}); +}); +}); + +}); diff --git a/addons/sms/static/src/components/message/message.xml b/addons/sms/static/src/components/message/message.xml new file mode 100644 index 00000000..b8b6a18d --- /dev/null +++ b/addons/sms/static/src/components/message/message.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<templates xml:space="preserve"> + + <t t-inherit="mail.Message" t-inherit-mode="extension"> + <xpath expr="//*[@name='failureIcon']" position="replace"> + <t t-if="message.message_type === 'sms'"> + <i class="o_Message_notificationIcon fa fa-mobile"/> SMS + </t> + <t t-else="">$0</t> + </xpath> + + <xpath expr="//*[@name='notificationIcon']" position="replace"> + <t t-if="message.message_type === 'sms'"> + <i class="o_Message_notificationIcon fa fa-mobile"/> SMS + </t> + <t t-else="">$0</t> + </xpath> + </t> + +</templates> diff --git a/addons/sms/static/src/components/message/message_tests.js b/addons/sms/static/src/components/message/message_tests.js new file mode 100644 index 00000000..57bc21ab --- /dev/null +++ b/addons/sms/static/src/components/message/message_tests.js @@ -0,0 +1,197 @@ +odoo.define('sms/static/src/components/message/message_tests.js', function (require) { +'use strict'; + +const components = { + Message: require('mail/static/src/components/message/message.js'), +}; +const { makeDeferred } = require('mail/static/src/utils/deferred/deferred.js'); +const { + afterEach, + afterNextRender, + beforeEach, + createRootComponent, + start, +} = require('mail/static/src/utils/test_utils.js'); + +const Bus = require('web.Bus'); + +QUnit.module('sms', {}, function () { +QUnit.module('components', {}, function () { +QUnit.module('message', {}, function () { +QUnit.module('message_tests.js', { + beforeEach() { + beforeEach(this); + + this.createMessageComponent = async (message, otherProps) => { + const props = Object.assign({ messageLocalId: message.localId }, otherProps); + await createRootComponent(this, components.Message, { + props, + target: this.widget.el, + }); + }; + + this.start = async params => { + const { env, widget } = await start(Object.assign({}, params, { + data: this.data, + })); + this.env = env; + this.widget = widget; + }; + }, + afterEach() { + afterEach(this); + }, +}); + +QUnit.test('Notification Sent', async function (assert) { + assert.expect(9); + + await this.start(); + const threadViewer = this.env.models['mail.thread_viewer'].create({ + hasThreadView: true, + thread: [['create', { + id: 11, + model: 'mail.channel', + }]], + }); + const message = this.env.models['mail.message'].create({ + id: 10, + message_type: 'sms', + notifications: [['insert', { + id: 11, + notification_status: 'sent', + notification_type: 'sms', + partner: [['insert', { id: 12, name: "Someone" }]], + }]], + originThread: [['link', threadViewer.thread]] + }); + await this.createMessageComponent(message, { + threadViewLocalId: threadViewer.threadView.localId + }); + + assert.containsOnce( + document.body, + '.o_Message', + "should display a message component" + ); + assert.containsOnce( + document.body, + '.o_Message_notificationIconClickable', + "should display the notification icon container" + ); + assert.containsOnce( + document.body, + '.o_Message_notificationIcon', + "should display the notification icon" + ); + assert.hasClass( + document.querySelector('.o_Message_notificationIcon'), + 'fa-mobile', + "icon should represent sms" + ); + + await afterNextRender(() => { + document.querySelector('.o_Message_notificationIconClickable').click(); + }); + assert.containsOnce( + document.body, + '.o_NotificationPopover', + "notification popover should be open" + ); + assert.containsOnce( + document.body, + '.o_NotificationPopover_notificationIcon', + "popover should have one icon" + ); + assert.hasClass( + document.querySelector('.o_NotificationPopover_notificationIcon'), + 'fa-check', + "popover should have the sent icon" + ); + assert.containsOnce( + document.body, + '.o_NotificationPopover_notificationPartnerName', + "popover should have the partner name" + ); + assert.strictEqual( + document.querySelector('.o_NotificationPopover_notificationPartnerName').textContent.trim(), + "Someone", + "partner name should be correct" + ); +}); + +QUnit.test('Notification Error', async function (assert) { + assert.expect(8); + + const openResendActionDef = makeDeferred(); + const bus = new Bus(); + bus.on('do-action', null, payload => { + assert.step('do_action'); + assert.strictEqual( + payload.action, + 'sms.sms_resend_action', + "action should be the one to resend sms" + ); + assert.strictEqual( + payload.options.additional_context.default_mail_message_id, + 10, + "action should have correct message id" + ); + openResendActionDef.resolve(); + }); + + await this.start({ env: { bus } }); + const threadViewer = this.env.models['mail.thread_viewer'].create({ + hasThreadView: true, + thread: [['create', { + id: 11, + model: 'mail.channel', + }]], + }); + const message = this.env.models['mail.message'].create({ + id: 10, + message_type: 'sms', + notifications: [['insert', { + id: 11, + notification_status: 'exception', + notification_type: 'sms', + }]], + originThread: [['link', threadViewer.thread]] + }); + await this.createMessageComponent(message, { + threadViewLocalId: threadViewer.threadView.localId + }); + + assert.containsOnce( + document.body, + '.o_Message', + "should display a message component" + ); + assert.containsOnce( + document.body, + '.o_Message_notificationIconClickable', + "should display the notification icon container" + ); + assert.containsOnce( + document.body, + '.o_Message_notificationIcon', + "should display the notification icon" + ); + assert.hasClass( + document.querySelector('.o_Message_notificationIcon'), + 'fa-mobile', + "icon should represent sms" + ); + document.querySelector('.o_Message_notificationIconClickable').click(); + await openResendActionDef; + assert.verifySteps( + ['do_action'], + "should do an action to display the resend sms dialog" + ); +}); + +}); +}); +}); + +}); diff --git a/addons/sms/static/src/components/notification_group/notification_group.js b/addons/sms/static/src/components/notification_group/notification_group.js new file mode 100644 index 00000000..053fedc5 --- /dev/null +++ b/addons/sms/static/src/components/notification_group/notification_group.js @@ -0,0 +1,27 @@ +odoo.define('sms/static/src/components/notification_group/notification_group.js', function (require) { +'use strict'; + +const components = { + NotificationGroup: require('mail/static/src/components/notification_group/notification_group.js'), +}; + +const { patch } = require('web.utils'); + +patch(components.NotificationGroup, 'sms/static/src/components/notification_group/notification_group.js', { + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + /** + * @override + */ + image() { + if (this.group.notification_type === 'sms') { + return '/sms/static/img/sms_failure.svg'; + } + return this._super(...arguments); + }, +}); + +}); diff --git a/addons/sms/static/src/components/notification_group/notification_group.xml b/addons/sms/static/src/components/notification_group/notification_group.xml new file mode 100644 index 00000000..c5f5a8db --- /dev/null +++ b/addons/sms/static/src/components/notification_group/notification_group.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?> +<templates xml:space="preserve"> + + <t t-inherit="mail.NotificationGroup" t-inherit-mode="extension"> + <xpath expr="//*[hasclass('o_NotificationGroup_inlineText')]" position="inside"> + <t t-if="group.notification_type === 'sms'"> + An error occurred when sending an SMS. + </t> + </xpath> + </t> + +</templates> diff --git a/addons/sms/static/src/components/notification_list/notification_list_notification_group_tests.js b/addons/sms/static/src/components/notification_list/notification_list_notification_group_tests.js new file mode 100644 index 00000000..bd5d8402 --- /dev/null +++ b/addons/sms/static/src/components/notification_list/notification_list_notification_group_tests.js @@ -0,0 +1,309 @@ +odoo.define('sms/static/src/components/notification_list/notification_list_notification_group_tests.js', function (require) { +'use strict'; + +const components = { + NotificationList: require('mail/static/src/components/notification_list/notification_list.js'), +}; + +const { + afterEach, + beforeEach, + createRootComponent, + start, +} = require('mail/static/src/utils/test_utils.js'); + +const Bus = require('web.Bus'); + +QUnit.module('sms', {}, function () { +QUnit.module('components', {}, function () { +QUnit.module('notification_list', {}, function () { +QUnit.module('notification_list_notification_group_tests.js', { + beforeEach() { + beforeEach(this); + + /** + * @param {Object} param0 + * @param {string} [param0.filter='all'] + */ + this.createNotificationListComponent = async ({ filter = 'all' } = {}) => { + await createRootComponent(this, components.NotificationList, { + props: { filter }, + target: this.widget.el, + }); + }; + + this.start = async params => { + const { env, widget } = await start(Object.assign({}, params, { + data: this.data, + })); + this.env = env; + this.widget = widget; + }; + }, + afterEach() { + afterEach(this); + }, +}); + +QUnit.test('mark as read', async function (assert) { + assert.expect(6); + + this.data['mail.message'].records.push( + // message that is expected to have a failure + { + id: 11, // random unique id, will be used to link failure to message + message_type: 'sms', // message must be sms (goal of the test) + model: 'mail.channel', // expected value to link message to channel + res_id: 31, // id of a random channel + } + ); + this.data['mail.notification'].records.push( + // failure that is expected to be used in the test + { + mail_message_id: 11, // id of the related message + notification_status: 'exception', // necessary value to have a failure + notification_type: 'sms', // expected failure type for sms message + } + ); + const bus = new Bus(); + bus.on('do-action', null, payload => { + assert.step('do_action'); + assert.strictEqual( + payload.action, + 'sms.sms_cancel_action', + "action should be the one to cancel sms" + ); + assert.strictEqual( + payload.options.additional_context.default_model, + 'mail.channel', + "action should have the group model as default_model" + ); + assert.strictEqual( + payload.options.additional_context.unread_counter, + 1, + "action should have the group notification length as unread_counter" + ); + }); + + await this.start({ env: { bus } }); + await this.createNotificationListComponent(); + + assert.containsOnce( + document.body, + '.o_NotificationGroup_markAsRead', + "should have 1 mark as read button" + ); + + document.querySelector('.o_NotificationGroup_markAsRead').click(); + assert.verifySteps( + ['do_action'], + "should do an action to display the cancel sms dialog" + ); +}); + +QUnit.test('notifications grouped by notification_type', async function (assert) { + assert.expect(11); + + this.data['mail.message'].records.push( + // first message that is expected to have a failure + { + id: 11, // random unique id, will be used to link failure to message + message_type: 'email', // different type from second message + model: 'res.partner', // same model as second message (and not `mail.channel`) + res_id: 31, // same res_id as second message + res_model_name: "Partner", // random related model name + }, + // second message that is expected to have a failure + { + id: 12, // random unique id, will be used to link failure to message + message_type: 'sms', // different type from first message + model: 'res.partner', // same model as first message (and not `mail.channel`) + res_id: 31, // same res_id as first message + res_model_name: "Partner", // same related model name for consistency + } + ); + this.data['mail.notification'].records.push( + // first failure that is expected to be used in the test + { + mail_message_id: 11, // id of the related first message + notification_status: 'exception', // necessary value to have a failure + notification_type: 'email', // different type from second failure + }, + // second failure that is expected to be used in the test + { + mail_message_id: 12, // id of the related second message + notification_status: 'exception', // necessary value to have a failure + notification_type: 'sms', // different type from first failure + } + ); + await this.start(); + await this.createNotificationListComponent(); + + assert.containsN( + document.body, + '.o_NotificationGroup', + 2, + "should have 2 notifications group" + ); + const groups = document.querySelectorAll('.o_NotificationGroup'); + assert.containsOnce( + groups[0], + '.o_NotificationGroup_name', + "should have 1 group name in first group" + ); + assert.strictEqual( + groups[0].querySelector('.o_NotificationGroup_name').textContent, + "Partner", + "should have model name as group name" + ); + assert.containsOnce( + groups[0], + '.o_NotificationGroup_counter', + "should have 1 group counter in first group" + ); + assert.strictEqual( + groups[0].querySelector('.o_NotificationGroup_counter').textContent.trim(), + "(1)", + "should have 1 notification in first group" + ); + assert.strictEqual( + groups[0].querySelector('.o_NotificationGroup_inlineText').textContent.trim(), + "An error occurred when sending an email.", + "should have the group text corresponding to email" + ); + assert.containsOnce( + groups[1], + '.o_NotificationGroup_name', + "should have 1 group name in second group" + ); + assert.strictEqual( + groups[1].querySelector('.o_NotificationGroup_name').textContent, + "Partner", + "should have second model name as group name" + ); + assert.containsOnce( + groups[1], + '.o_NotificationGroup_counter', + "should have 1 group counter in second group" + ); + assert.strictEqual( + groups[1].querySelector('.o_NotificationGroup_counter').textContent.trim(), + "(1)", + "should have 1 notification in second group" + ); + assert.strictEqual( + groups[1].querySelector('.o_NotificationGroup_inlineText').textContent.trim(), + "An error occurred when sending an SMS.", + "should have the group text corresponding to sms" + ); +}); + +QUnit.test('grouped notifications by document model', async function (assert) { + // If all failures linked to a document model refers to different documents, + // a single notification should group all failures that are linked to this + // document model. + assert.expect(12); + + this.data['mail.message'].records.push( + // first message that is expected to have a failure + { + id: 11, // random unique id, will be used to link failure to message + message_type: 'sms', // message must be sms (goal of the test) + model: 'res.partner', // same model as second message (and not `mail.channel`) + res_id: 31, // different res_id from second message + res_model_name: "Partner", // random related model name + }, + // second message that is expected to have a failure + { + id: 12, // random unique id, will be used to link failure to message + message_type: 'sms', // message must be sms (goal of the test) + model: 'res.partner', // same model as first message (and not `mail.channel`) + res_id: 32, // different res_id from first message + res_model_name: "Partner", // same related model name for consistency + } + ); + this.data['mail.notification'].records.push( + // first failure that is expected to be used in the test + { + mail_message_id: 11, // id of the related first message + notification_status: 'exception', // necessary value to have a failure + notification_type: 'sms', // expected failure type for sms message + }, + // second failure that is expected to be used in the test + { + mail_message_id: 12, // id of the related second message + notification_status: 'exception', // necessary value to have a failure + notification_type: 'sms', // expected failure type for sms message + } + ); + const bus = new Bus(); + bus.on('do-action', null, payload => { + assert.step('do_action'); + assert.strictEqual( + payload.action.name, + "SMS Failures", + "action should have 'SMS Failures' as name", + ); + assert.strictEqual( + payload.action.type, + 'ir.actions.act_window', + "action should have the type act_window" + ); + assert.strictEqual( + payload.action.view_mode, + 'kanban,list,form', + "action should have 'kanban,list,form' as view_mode" + ); + assert.strictEqual( + JSON.stringify(payload.action.views), + JSON.stringify([[false, 'kanban'], [false, 'list'], [false, 'form']]), + "action should have correct views" + ); + assert.strictEqual( + payload.action.target, + 'current', + "action should have 'current' as target" + ); + assert.strictEqual( + payload.action.res_model, + 'res.partner', + "action should have the group model as res_model" + ); + assert.strictEqual( + JSON.stringify(payload.action.domain), + JSON.stringify([['message_has_sms_error', '=', true]]), + "action should have 'message_has_sms_error' as domain" + ); + }); + + await this.start({ env: { bus } }); + await this.createNotificationListComponent(); + + assert.containsOnce( + document.body, + '.o_NotificationGroup', + "should have 1 notification group" + ); + assert.containsOnce( + document.body, + '.o_NotificationGroup_counter', + "should have 1 group counter" + ); + assert.strictEqual( + document.querySelector('.o_NotificationGroup_counter').textContent.trim(), + "(2)", + "should have 2 notifications in the group" + ); + + document.querySelector('.o_NotificationGroup').click(); + assert.verifySteps( + ['do_action'], + "should do an action to display the related records" + ); +}); + +}); +}); +}); + +}); diff --git a/addons/sms/static/src/js/fields_phone_widget.js b/addons/sms/static/src/js/fields_phone_widget.js new file mode 100644 index 00000000..82b8e5b3 --- /dev/null +++ b/addons/sms/static/src/js/fields_phone_widget.js @@ -0,0 +1,100 @@ +odoo.define('sms.fields', function (require) { +"use strict"; + +var basic_fields = require('web.basic_fields'); +var core = require('web.core'); +var session = require('web.session'); + +var _t = core._t; + +/** + * Override of FieldPhone to add a button calling SMS composer if option activated (default) + */ + +var Phone = basic_fields.FieldPhone; +Phone.include({ + /** + * By default, enable_sms is activated + * + * @override + */ + init() { + this._super.apply(this, arguments); + this.enableSMS = 'enable_sms' in this.attrs.options ? this.attrs.options.enable_sms : true; + // reinject in nodeOptions (and thus in this.attrs) to signal the property + this.attrs.options.enable_sms = this.enableSMS; + }, + /** + * When the send SMS button is displayed, $el becomes a div wrapping + * the original links. + * This method makes sure we always focus the phone number + * + * @override + */ + getFocusableElement() { + if (this.enableSMS && this.mode === 'readonly') { + return this.$el.filter('.' + this.className); + } + return this._super.apply(this, arguments); + }, + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * Open SMS composer wizard + * + * @private + */ + _onClickSMS: function (ev) { + ev.preventDefault(); + ev.stopPropagation(); + + var context = session.user_context; + context = _.extend({}, context, { + default_res_model: this.model, + default_res_id: parseInt(this.res_id), + default_number_field_name: this.name, + default_composition_mode: 'comment', + }); + var self = this; + return this.do_action({ + title: _t('Send SMS Text Message'), + type: 'ir.actions.act_window', + res_model: 'sms.composer', + target: 'new', + views: [[false, 'form']], + context: context, + }, { + on_close: function () { + self.trigger_up('reload'); + }}); + }, + + /** + * Add a button to call the composer wizard + * + * @override + * @private + */ + _renderReadonly: function () { + var def = this._super.apply(this, arguments); + if (this.enableSMS && this.value) { + var $composerButton = $('<a>', { + title: _t('Send SMS Text Message'), + href: '', + class: 'ml-3 d-inline-flex align-items-center o_field_phone_sms', + html: $('<small>', {class: 'font-weight-bold ml-1', html: 'SMS'}), + }); + $composerButton.prepend($('<i>', {class: 'fa fa-mobile'})); + $composerButton.on('click', this._onClickSMS.bind(this)); + this.$el = this.$el.add($composerButton); + } + + return def; + }, +}); + +return Phone; + +}); diff --git a/addons/sms/static/src/js/fields_sms_widget.js b/addons/sms/static/src/js/fields_sms_widget.js new file mode 100644 index 00000000..5a52d68e --- /dev/null +++ b/addons/sms/static/src/js/fields_sms_widget.js @@ -0,0 +1,185 @@ +odoo.define('sms.sms_widget', function (require) { +"use strict"; + +var core = require('web.core'); +var fieldRegistry = require('web.field_registry'); +var FieldTextEmojis = require('mail.field_text_emojis'); + +var _t = core._t; +/** + * SmsWidget is a widget to display a textarea (the body) and a text representing + * the number of SMS and the number of characters. This text is computed every + * time the user changes the body. + */ +var SmsWidget = FieldTextEmojis.extend({ + className: 'o_field_text', + enableEmojis: false, + /** + * @constructor + */ + init: function () { + this._super.apply(this, arguments); + this.nbrChar = 0; + this.nbrSMS = 0; + this.encoding = 'GSM7'; + this.enableEmojis = !!this.nodeOptions.enable_emojis; + }, + + /** + * @override + *"This will add the emoji dropdown to a target field (controlled by the "enableEmojis" attribute) + */ + on_attach_callback: function () { + if (this.enableEmojis) { + this._super.apply(this, arguments); + } + }, + + //-------------------------------------------------------------------------- + // Private: override widget + //-------------------------------------------------------------------------- + + /** + * @private + * @override + */ + _renderEdit: function () { + var def = this._super.apply(this, arguments); + + this._compute(); + $('.o_sms_container').remove(); + var $sms_container = $('<div class="o_sms_container"/>'); + $sms_container.append(this._renderSMSInfo()); + $sms_container.append(this._renderIAPButton()); + this.$el = this.$el.add($sms_container); + + return def; + }, + + //-------------------------------------------------------------------------- + // Private: SMS + //-------------------------------------------------------------------------- + + /** + * Compute the number of characters and sms + * @private + */ + _compute: function () { + var content = this._getValue(); + this.encoding = this._extractEncoding(content); + this.nbrChar = content.length; + this.nbrChar += (content.match(/\n/g) || []).length; + this.nbrSMS = this._countSMS(this.nbrChar, this.encoding); + }, + + /** + * Count the number of SMS of the content + * @private + * @returns {integer} Number of SMS + */ + _countSMS: function () { + if (this.nbrChar === 0) { + return 0; + } + if (this.encoding === 'UNICODE') { + if (this.nbrChar <= 70) { + return 1; + } + return Math.ceil(this.nbrChar / 67); + } + if (this.nbrChar <= 160) { + return 1; + } + return Math.ceil(this.nbrChar / 153); + }, + + /** + * Extract the encoding depending on the characters in the content + * @private + * @param {String} content Content of the SMS + * @returns {String} Encoding of the content (GSM7 or UNICODE) + */ + _extractEncoding: function (content) { + if (String(content).match(RegExp("^[@£$¥èéùìòÇ\\nØø\\rÅåΔ_ΦΓΛΩΠΨΣΘΞÆæßÉ !\\\"#¤%&'()*+,-./0123456789:;<=>?¡ABCDEFGHIJKLMNOPQRSTUVWXYZÄÖÑܧ¿abcdefghijklmnopqrstuvwxyzäöñüà]*$"))) { + return 'GSM7'; + } + return 'UNICODE'; + }, + + /** + * Render the IAP button to redirect to IAP pricing + * @private + */ + _renderIAPButton: function () { + return $('<a>', { + 'href': 'https://iap-services.odoo.com/iap/sms/pricing', + 'target': '_blank', + 'title': _t('SMS Pricing'), + 'aria-label': _t('SMS Pricing'), + 'class': 'fa fa-lg fa-info-circle', + }); + }, + + /** + * Render the number of characters, sms and the encoding. + * @private + */ + _renderSMSInfo: function () { + var string = _.str.sprintf(_t('%s characters, fits in %s SMS (%s) '), this.nbrChar, this.nbrSMS, this.encoding); + var $span = $('<span>', { + 'class': 'text-muted o_sms_count', + }); + $span.text(string); + return $span; + }, + + /** + * Update widget SMS information with re-computed info about length, ... + * @private + */ + _updateSMSInfo: function () { + this._compute(); + var string = _.str.sprintf(_t('%s characters, fits in %s SMS (%s) '), this.nbrChar, this.nbrSMS, this.encoding); + this.$('.o_sms_count').text(string); + }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * @override + * @private + */ + _onBlur: function () { + var content = this._getValue(); + if( !content.trim().length && content.length > 0) { + this.do_warn(_t("Your SMS Text Message must include at least one non-whitespace character")); + this.$input.val(content.trim()); + this._updateSMSInfo(); + } + }, + + /** + * @override + * @private + */ + _onChange: function () { + this._super.apply(this, arguments); + this._updateSMSInfo(); + }, + + /** + * @override + * @private + */ + _onInput: function () { + this._super.apply(this, arguments); + this._updateSMSInfo(); + }, +}); + +fieldRegistry.add('sms_widget', SmsWidget); + +return SmsWidget; +}); diff --git a/addons/sms/static/src/models/message/message.js b/addons/sms/static/src/models/message/message.js new file mode 100644 index 00000000..2f468cc0 --- /dev/null +++ b/addons/sms/static/src/models/message/message.js @@ -0,0 +1,33 @@ +odoo.define('sms/static/src/models/message/message.js', function (require) { +'use strict'; + +const { + registerInstancePatchModel, +} = require('mail/static/src/model/model_core.js'); + +registerInstancePatchModel('mail.message', 'sms/static/src/models/message/message.js', { + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + /** + * @override + */ + openResendAction() { + if (this.message_type === 'sms') { + this.env.bus.trigger('do-action', { + action: 'sms.sms_resend_action', + options: { + additional_context: { + default_mail_message_id: this.id, + }, + }, + }); + } else { + this._super(...arguments); + } + }, +}); + +}); diff --git a/addons/sms/static/src/models/notification_group/notification_group.js b/addons/sms/static/src/models/notification_group/notification_group.js new file mode 100644 index 00000000..887d9a7d --- /dev/null +++ b/addons/sms/static/src/models/notification_group/notification_group.js @@ -0,0 +1,62 @@ +odoo.define('sms/static/src/models/notification_group/notification_group.js', function (require) { +'use strict'; + +const { + registerInstancePatchModel, +} = require('mail/static/src/model/model_core.js'); + +registerInstancePatchModel('mail.notification_group', 'sms/static/src/models/notification_group/notification_group.js', { + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + /** + * @override + */ + openCancelAction() { + if (this.notification_type !== 'sms') { + return this._super(...arguments); + } + this.env.bus.trigger('do-action', { + action: 'sms.sms_cancel_action', + options: { + additional_context: { + default_model: this.res_model, + unread_counter: this.notifications.length, + }, + }, + }); + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * @override + */ + _openDocuments() { + if (this.notification_type !== 'sms') { + return this._super(...arguments); + } + this.env.bus.trigger('do-action', { + action: { + name: this.env._t("SMS Failures"), + type: 'ir.actions.act_window', + view_mode: 'kanban,list,form', + views: [[false, 'kanban'], [false, 'list'], [false, 'form']], + target: 'current', + res_model: this.res_model, + domain: [['message_has_sms_error', '=', true]], + }, + }); + if (this.env.messaging.device.isMobile) { + // messaging menu has a higher z-index than views so it must + // be closed to ensure the visibility of the view + this.env.messaging.messagingMenu.close(); + } + }, +}); + +}); |
