diff --git a/i18n/de-DE.json b/i18n/de-DE.json index 57fda0a..c041c40 100644 --- a/i18n/de-DE.json +++ b/i18n/de-DE.json @@ -687,12 +687,8 @@ } } }, - "vm_to_email": { - "deleteAfterNotify": "Nach dem Senden der Benachrichtigung Nachrichten aus Datenbank löschen.", - "headline": "Voicemail-zu-E-Mail-Einstellungen", - "help": "Wenn Sie das Sendeziel von E-Mail-Benachrichtigungen ändern möchten, bearbeiten Sie die E-Mail-Adresse des Benutzers im Abschnitt „Benutzeradministration“", - "label": "Für jede neue Voicemail wird eine E-Mail an {{variable}} gesendet", - "title": "Voicemails" + "vmbox": { + "deleteAfterNotify": "Nach dem Senden der Benachrichtigung Nachrichten aus Datenbank löschen." }, "find_me_follow_me": { "title": "Find Me/Follow Me", @@ -767,23 +763,6 @@ "__version": "4.0", "confirmMobileUnAssignment": "Sie versuchen gerade, die Zuweisung eines mobilen Geräts aufzuheben. Beachten Sie, dass dadurch auch die Verknüpfung der zu diesem Gerät ({{variable}}) zugehörigen Telefonnummer mit diesem Benutzer aufgehoben wird. Möchten Sie den Vorgang fortsetzen?", - "__comment": "UI-2138: Neues Dialogfeld: Benutzer löschen", - "__version": "3.22", - "deleteDialog": { - "title": "Löschen von {{ name }} aus dem System", - "headerAdmin": "Sie versuchen gerade, einen ADMIN zu löschen! Wenn man einen Admin löscht, werden sein Benutzer und seine Voicemailbox gelöscht, und seine Geräte sind ihm nicht länger zugewiesen. Außerdem wird dieser Admin dann nicht mehr in der Lage sein, diese UI zu verwalten. Möchten Sie den Vorgang wirklich fortsetzen?", - "headerUser": "Durch das Löschen eines Benutzers werden dieser Benutzer und seine Voicemailboxen gelöscht. Seine Geräte und Nummern sind ihm nicht länger zugewiesen. Möchten Sie den Vorgang fortsetzen?", - "deleteUser": "Löschen", - "deleteUserName": "„{{variable}}“ löschen", - "proceed": "Fortfahren", - "deleteDevices": "Die Geräte dieses Benutzers ebenfalls entfernen", - "deleteDevicesHelp": "Wenn Sie diese Option nicht aktivieren, verlieren diese Geräte ihre Zuweisung und tauchen in der Liste freier Geräte auf.", - "__comment": "UI-2467: Beim Löschen eines Benutzers in Smart PBX werden auch Konferenzen entfernt", - "__version": "4.0", - "deleteConferences": "Die Konferenzen dieses Benutzers ebenfalls entfernen", - "deleteConferencesHelp": "Wenn Sie diese Option deaktiviert lassen, werden Sie die Konferenznummer des Benutzers nirgends anders verwenden können, da sie weiterhin von dieser Konferenz belegt sein wird." - }, - "__comment": "UI-2921: Neues Dialogfeld: Benutzer löschen", "__version": "4.2", "impersonate": "Identität annehmen", diff --git a/i18n/en-US.json b/i18n/en-US.json index 9b9d6a0..d9d4ec9 100644 --- a/i18n/en-US.json +++ b/i18n/en-US.json @@ -150,8 +150,8 @@ "__comment": "UI-2104: Adding a warning when adding a group with no endpoints", "__version": "3.22", "emptyEndpointsWarning": "You need to select at least one member to create a group", - "ringGroup": "{{name}} Ring Group", - "baseGroup": "{{name}} Base Group" + "ringGroup": "{{{name}}} Ring Group", + "baseGroup": "{{{name}}} Base Group" }, "devices": { @@ -684,12 +684,13 @@ } } }, - "vm_to_email": { + "vmbox": { "deleteAfterNotify": "Delete messages from database after sending the notification.", - "headline": "Voicemail-to-Email Settings", - "help": "If you want to change where email notifications go to, please edit the user's email address in the User Administration section", - "label": "An e-mail will be sent to {{variable}} for every new voicemail", - "title": "Voicemails" + "headline": "User Voicemail Box", + "label": "A voicemail box will be available for this user.", + "sendVmToEmail": "Send an e-mail to {{variable}} for every new voicemail.", + "title": "Voicemail Box", + "vmToEmailHelp": "An e-mail will be sent to {{variable}} for every new voicemail. If you want to change where email notifications go to, please edit the user's email address in the User Administration section." }, "find_me_follow_me": { "title": "Find me, Follow me", @@ -764,23 +765,6 @@ "__version": "4.0", "confirmMobileUnAssignment": "You are about to un-assign a Mobile device. Be aware the phone number associated with this device ({{variable}}) will also be un-assigned from this user. Are you sure?", - "__comment": "UI-2138: Delete User new Dialog", - "__version": "3.22", - "deleteDialog": { - "title": "Remove {{ name }} from the system", - "headerAdmin": "You're about to delete an ADMIN! Deleting an admin will delete his user, voicemail boxes and unassign his devices. It will also prevent them from managing this UI. Do you want to continue?", - "headerUser": "Deleting a user will delete this user and his Voicemail Box, and unassign the devices and numbers that were assigned to this user. Do you want to continue?", - "deleteUser": "Delete", - "deleteUserName": "Delete \"{{variable}}\"", - "proceed": "Proceed", - "deleteDevices": "Also remove this user's devices from the system", - "deleteDevicesHelp": "If you leave this option unchecked, we'll just unassign these devices and they will show up in the list of Spare Devices", - "__comment": "UI-2467: also remove conferences when deleting a user from smartpbx", - "__version": "4.0", - "deleteConferences": "Also remove this user's conferences from the system", - "deleteConferencesHelp": "If you leave this option unchecked, you won't be able to use the user's conference number anywhere else, as it will still be used by that conference." - }, - "__comment": "UI-2921: Delete User new Dialog", "__version": "4.2", "impersonate": "Impersonate", diff --git a/i18n/es-ES.json b/i18n/es-ES.json index 3e9874a..1c118d5 100644 --- a/i18n/es-ES.json +++ b/i18n/es-ES.json @@ -569,12 +569,8 @@ } } }, - "vm_to_email": { - "deleteAfterNotify": "Eliminar los mensajes de la base de datos después de enviar la notificación.", - "headline": "Configuración de correo de voz a correo electrónico", - "help": "Si quiere cambiar a donde las notificaciones del correo electrónico van, por favor corrija la dirección de correo electrónico del usuario en la sección de la Administración del Usuario", - "label": "Se enviará un mensaje de correo electrónico a {{variable}} para cada nuevo mensaje de correo de voz", - "title": "correos de voz" + "vmbox": { + "deleteAfterNotify": "Eliminar los mensajes de la base de datos después de enviar la notificación." }, "find_me_follow_me": { "title": "Búscame, Sígueme ", @@ -1078,4 +1074,4 @@ "__comment": "UI-1153: Show warning icon next to numbers being transfered", "__version": "v3.20_s3", "portIconHelp": "Este número está en proceso de ser portado y serán eliminados si el puerto falla. Sólo aparece para el establecimiento de objetivos y no está configurado para manejar las llamadas" -} \ No newline at end of file +} diff --git a/i18n/fr-FR.json b/i18n/fr-FR.json index 7929df1..18a2281 100644 --- a/i18n/fr-FR.json +++ b/i18n/fr-FR.json @@ -543,12 +543,8 @@ } } }, - "vm_to_email": { - "deleteAfterNotify": "Supprimer les messages vocaux de la base de données après l'envoi de la notification.", - "headline": "Messages vocaux vers Email", - "help": "Si vous souhaitez changer l'email qui recevra les notifications, vous pouvez mettre à jour l'adresse e-mail dans les réglages utilisateurs", - "label": "Un email sera envoyé à {{variable}} à chaque nouveau message vocal", - "title": "Messages vocaux" + "vmbox": { + "deleteAfterNotify": "Supprimer les messages vocaux de la base de données après l'envoi de la notification." }, "find_me_follow_me": { "title": "Ordre d'appel", diff --git a/i18n/ru-RU.json b/i18n/ru-RU.json index f64a0ae..160e5e9 100644 --- a/i18n/ru-RU.json +++ b/i18n/ru-RU.json @@ -554,12 +554,8 @@ } } }, - "vm_to_email": { - "deleteAfterNotify": "Удалите сообщения из базы данных после отправки уведомления.", - "headline": "Настройка отправки голосовых сообщений на электроную почту", - "help": "Если вы собираетесь изменить адрес электронной почты, куда должны приходить уведомления, пожалуйста отредактируйте адрес электронной почты пользователя в меню администрирования пользователей", - "label": "Собщения будут отправлены на адрес {{variable}} электронной почты для всех новых голосовых сообщений", - "title": "Голосовая почта" + "vmbox": { + "deleteAfterNotify": "Удалите сообщения из базы данных после отправки уведомления." }, "find_me_follow_me": { "title": "Найди-меня-Следуй-за-мной", @@ -618,19 +614,6 @@ } }, "confirmMobileUnAssignment": "Вы собираетесь открепить мобильное устройство. Для информации, номер телефона ассоциированный с устройством ({{variable}}) также будет откреплен от данного пользователя. Вы уверены?", - - "deleteDialog": { - "title": "Удалить {{ name }} из системы", - "headerAdmin": "Вы собираетесь удалить АДМИНИСТРАТОРА! Удаление администратора также удалит его голосовой почтовый ящик и открепит устройства и номера ранее закрепленные за пользователем. Также в дальнейшем он не сможет управлять через данный пользовательский интерфейс. Хотите продолжить?", - "headerUser": "Удаление пользователя также удалит его голосовой почтовый ящик и открепит устройства и номера ранее закрепленные за пользователем. Хотите продолжить?", - "deleteUser": "Удалить", - "deleteUserName": "Удалить \"{{variable}}\"", - "proceed": "Выполняем", - "deleteDevices": "Также удалить устройства пользователя из системы", - "deleteDevicesHelp": "Если оставите эту опцию не выделенной, мы просто открепим его устройства и они будут отображаться в списке Доступных Устройств", - "deleteConferences": "Также удалить конференц-комнаты пользователя из системы", - "deleteConferencesHelp": "Если оставите эту опцию не выделенной, Вы не сможете использовать номер конференции пользователя где-либо еще, так как он будет использоваться данной конференцией." - } }, "strategy": { diff --git a/submodules/groups/groups.js b/submodules/groups/groups.js index 6650c80..2c87159 100644 --- a/submodules/groups/groups.js +++ b/submodules/groups/groups.js @@ -350,7 +350,8 @@ define(function(require) { }, groupsCreationMergeData: function(data, template) { - var formData = monster.ui.getFormData('form_group_creation'), + var self = this, + formData = monster.ui.getFormData('form_group_creation'), fixedTimeout = '20', fixedDelay = '0', settings = { @@ -376,7 +377,12 @@ define(function(require) { }, baseCallflow: { numbers: [ monster.util.randomString(25) ], - name: formData.name + ' Base Group', + name: self.getTemplate({ + name: '!' + self.i18n.active().groups.baseGroup, + data: { + name: formData.name + } + }), flow: { module: 'ring_group', children: {}, @@ -390,7 +396,12 @@ define(function(require) { }, callflow: { numbers: [ formData.extra.extension ], - name: formData.name + ' Ring Group', + name: self.getTemplate({ + name: '!' + self.i18n.active().groups.ringGroup, + data: { + name: formData.name + } + }), flow: { module: 'callflow', children: { diff --git a/submodules/strategy/strategy.js b/submodules/strategy/strategy.js index d28f944..0dda43a 100644 --- a/submodules/strategy/strategy.js +++ b/submodules/strategy/strategy.js @@ -2397,26 +2397,22 @@ define(function(require) { monster.ui.confirm(self.i18n.active().strategy.confirmMessages.resetCalls, function() { monster.waterfall([ function(callback) { - self.strategyDeleteCalls({ - success: function() { - callback(null); - }, - error: function() { - callback(true); - } - }); + self.strategyGetSubCallflows(callback); }, - function(callback) { - self.strategyGetMainCallflows(function(callflows) { - strategyData.callflows = callflows; - callback(null, callflows); + function(mainSubCallflows, callback) { + self.strategyResetSubCallStrategies({ + mainSubCallflows: mainSubCallflows, + callback: callback }); } - ], function(err, result) { + ], function(err, callflows) { if (err) { return; } + // Update modified callflows on strategyData + _.assign(strategyData.callflows, callflows); + container.hide(); container.parents('.element-container').removeClass('open'); monster.ui.toast({ @@ -3100,18 +3096,7 @@ define(function(require) { function(innerCallback) { self.strategyCreateMenu({ data: { - data: { - name: menuName, - record_pin: monster.util.randomString(4, '1234567890'), - media: { - exit_media: true, - invalid_media: true, - transfer_media: true - }, - retries: 3, - max_extension_length: 4, - type: 'main' - } + data: self.strategyGetDefaultMainSubMenu(menuName) }, success: function(menuData) { innerCallback(null, menuData); @@ -3124,20 +3109,7 @@ define(function(require) { function(menuData, innerCallback) { self.strategyCreateCallflow({ data: { - data: { - contact_list: { - exclude: false - }, - numbers: [menuName], - type: 'main', - flow: { - children: {}, - data: { - id: menuData.id - }, - module: 'menu' - } - } + data: self.strategyGetDefaultMainSubMenuCallflow(menuData) }, success: function(data) { innerCallback(null, data); @@ -3162,20 +3134,10 @@ define(function(require) { parallelRequests[val] = function(callback) { self.strategyCreateCallflow({ data: { - data: { - contact_list: { - exclude: false - }, - numbers: [val], - type: 'main', - flow: { - children: {}, - data: { - id: mainCallflows[val + 'Menu'].id - }, - module: 'callflow' - } - } + data: self.strategyGetDefaultMainSubCallflow({ + label: val, + subMenuCallflowId: mainCallflows[val + 'Menu'].id + }) }, success: function(data) { callback(null, data); @@ -3192,12 +3154,12 @@ define(function(require) { self.strategyUpdateCallflow(results.MainCallflow, function(updatedCallflow) { results.MainCallflow = updatedCallflow; - waterfallCallback(null, $.extend(true, mainCallflows, results)); + waterfallCallback(null, _.merge(mainCallflows, results)); }); return; } - waterfallCallback(null, $.extend(true, mainCallflows, results)); + waterfallCallback(null, _.merge(mainCallflows, results)); return; } @@ -3971,200 +3933,177 @@ define(function(require) { }); }, - strategyDeleteCalls: function(args) { + /** + * Request the creation of a menu to the API + * @param {Object} args + * @param {Object} args.data + * @param {Object} args.data.data Menu object to be created + * @param {Function} [args.success] Success callback + * @param {Function} [args.error] Error callback + */ + strategyCreateMenu: function(args) { var self = this; - monster.waterfall([ - // Get main callflows created via SmartPBX - function(callback) { - self.strategyListCallflows({ - filters: { - 'paginate': false, - 'has_value': 'type', - 'filter_type': 'main', - 'key_missing': [ - 'owner_id', - 'group_id' - ], - 'filter_ui_metadata.origin': [ - 'voip' - ] - }, - success: function(data) { - // Convert callflows array to map object, then send to next step - callback(null, - _.reduce(data, function(obj, callflow) { - var label = callflow.name || callflow.numbers[0]; - obj[label] = callflow; - return obj; - }, {})); - }, - error: function(parsedError) { - callback(parsedError); - } - }); + self.callApi({ + resource: 'menu.create', + data: _.merge({ + accountId: self.accountId + }, args.data), + success: function(data) { + args.hasOwnProperty('success') && args.success(data.data); }, - // Delete menus and callflows - function(mainCallflows, callback) { - monster.parallel( - _.reduce(self.subCallflowsLabel, function(parallelCalls, label) { - var deleteSequence = self.strategyCreateSingleCallStrategyDeleteSequence(label, mainCallflows); - - if (_.isEmpty(deleteSequence)) { - return parallelCalls; - } - - parallelCalls.push(function(callback) { - monster.waterfall(deleteSequence, function(err, results) { - callback(null); - }); - }); - - return parallelCalls; - }, []), - function(err, results) { - if (err) { - callback(err); - return; - } - callback(null); - }); - } - ], function(err, results) { - if (err) { - args.hasOwnProperty('error') && args.error(err); - return; + error: function(parsedError) { + args.hasOwnProperty('error') && args.error(parsedError); } - args.hasOwnProperty('success') && args.success(results); }); }, - strategyCreateSingleCallStrategyDeleteSequence: function(label, mainCallflows) { - var self = this, - deleteSequence = [], - strategyCallflow = mainCallflows[label], - menuCallflow = mainCallflows[label + 'Menu']; - - if (strategyCallflow) { - // Add function to delete call strategy callflow - deleteSequence.push(function(callback) { - self.strategyDeleteCallflow({ - data: { - id: strategyCallflow.id - }, - success: function() { - callback(null); - }, - error: function() { - callback(true); - } - }); - }); - } - - if (!menuCallflow) { - return deleteSequence; - } - - // There is a menu callflow, so create functions to... - - // ...get callflow details (to get menu ID),... - deleteSequence.push(function(callback) { - self.strategyGetCallflow({ - data: { - id: menuCallflow.id - }, - success: function(menuCallflowDetails) { - callback(null, menuCallflowDetails); - }, - error: function() { - callback(true); - } - }); - }); - - // ...then delete menu callflow - deleteSequence.push(function(menuCallflowDetails, callback) { - self.strategyDeleteCallflow({ - data: { - id: menuCallflow.id - }, - success: function() { - callback(null, menuCallflowDetails); - }, - error: function() { - callback(true); - } - }); - }); + /** + * Gets a main sub menu for call handling strategies, with the default structure + * @param {String} menuName Menu name + */ + strategyGetDefaultMainSubMenu: function(menuName) { + return { + name: menuName, + record_pin: monster.util.randomString(4, '1234567890'), + media: { + exit_media: true, + invalid_media: true, + transfer_media: true + }, + retries: 3, + max_extension_length: 4, + type: 'main' + }; + }, - // ...and finally delete menu - deleteSequence.push(function(menuCallflowDetails, callback) { - self.strategyDeleteMenu({ + /** + * Gets a main callflow for a call handling strategy menu, with the default structure + * and values + * @param {Object} menuData + * @param {String} menuData.name Menu name + * @param {String} menuData.id Menu ID + * @returns {Object} Sub menu callflow + */ + strategyGetDefaultMainSubMenuCallflow: function(menuData) { + return { + contact_list: { + exclude: false + }, + numbers: [ menuData.name ], + type: 'main', + flow: { + children: {}, data: { - id: menuCallflowDetails.flow.data.id - }, - success: function() { - callback(null); + id: menuData.id }, - error: function() { - callback(true); - } - }); - }); - - return deleteSequence; + module: 'menu' + } + }; }, - strategyCreateMenu: function(args) { - var self = this; - self.callApi({ - resource: 'menu.create', - data: { - accountId: self.accountId, - data: args.data.data - }, - success: function(data, status) { - args.hasOwnProperty('success') && args.success(data.data); + /** + * Gets a main callflow for call handling strategies, with the default structure and + * values + * @param {Object} args + * @param {String} args.label Callflow label, to be set as number + * @param {String} args.subMenuCallflowId ID of the sub menu callflow + * @returns {Object} Callflow for call handling strategy + */ + strategyGetDefaultMainSubCallflow: function(args) { + return { + contact_list: { + exclude: false }, - error: function(parsedError) { - args.hasOwnProperty('error') && args.error(parsedError); + numbers: [ args.label ], + type: 'main', + flow: { + children: {}, + data: { + id: args.subMenuCallflowId + }, + module: 'callflow' } - }); + }; }, - strategyDeleteMenu: function(args) { + /** + * Get main callflows for call handling strategies + * @param {Function} callback Callback function for monster async tasks + */ + strategyGetSubCallflows: function(callback) { var self = this; - self.callApi({ - resource: 'menu.delete', - data: { - accountId: self.accountId, - menuId: args.data.id + + self.strategyListCallflows({ + filters: { + paginate: false, + has_value: 'type', + filter_type: 'main', + key_missing: [ + 'owner_id', + 'group_id' + ], + 'filter_ui_metadata.origin': [ + 'voip' + ] }, - success: function(data, status) { - args.hasOwnProperty('success') && args.success(data.data); + success: function(data) { + // Convert callflows array to map object, then send to next step + callback(null, + _.reduce(data, function(obj, callflow) { + var label = callflow.name || callflow.numbers[0]; + obj[label] = callflow; + return obj; + }, {})); }, error: function(parsedError) { - args.hasOwnProperty('error') && args.error(parsedError); + callback(parsedError); } }); }, - strategyDeleteCallflow: function(args) { + /** + * Reset call handling strategies + * @param {Object} args.mainSubCallflows Account main callflows + * @param {Function} args.callback Callback function for monster async tasks + */ + strategyResetSubCallStrategies: function(args) { var self = this; - self.callApi({ - resource: 'callflow.delete', - data: { - accountId: self.accountId, - callflowId: args.data.id - }, - success: function(data, status) { - args.hasOwnProperty('success') && args.success(data.data); - }, - error: function(parsedError) { - args.hasOwnProperty('error') && args.error(parsedError); - } - }); + + monster.parallel( + _.reduce(self.subCallflowsLabel, function(callflowRequests, label) { + callflowRequests[label] = function(callback) { + var mainSubCallflows = args.mainSubCallflows, + callflow = self.strategyGetDefaultMainSubCallflow({ + label: label, + subMenuCallflowId: mainSubCallflows[label + 'Menu'].id + }), + callflowArgs = { + data: { + data: callflow + }, + success: function(savedCallflow) { + callback(null, savedCallflow); + }, + error: function(parsedError) { + callback(parsedError); + } + }; + + if (!mainSubCallflows.hasOwnProperty(label)) { + self.strategyCreateCallflow(callflowArgs); + return; + } + + var callflowToUpdate = mainSubCallflows[label]; + callflow.id = callflowToUpdate.id; + self.strategyUpdateCallflow(callflow, function(savedCallflow) { + callback(null, savedCallflow); + }); + }; + + return callflowRequests; + }, {}), args.callback); } }; diff --git a/submodules/users/users.js b/submodules/users/users.js index 865513f..723b76c 100644 --- a/submodules/users/users.js +++ b/submodules/users/users.js @@ -171,14 +171,29 @@ define(function(require) { }); }, - usersFormatUserData: function(dataUser, _mainDirectory, _mainCallflow, _vmbox, _vmboxes) { + /** + * Format user related data + * @param {Object} data + * @param {Object} data.user User data + * @param {Object} data.userMainCallflow User's main callflow + * @param {Object} data.userVMBox User's main voicemail box + * @param {Object} data.existingVmboxes Account's existing voicemail boxes + * @param {Object} data.mainDirectory Account's main directory + */ + usersFormatUserData: function(data) { var self = this, + dataUser = data.user, + _mainDirectory = data.mainDirectory, + _mainCallflow = data.userMainCallflow, + _vmbox = data.userVMBox, + _vmboxes = data.existingVmboxes, formattedUser = { additionalDevices: 0, additionalExtensions: 0, additionalNumbers: 0, devices: [], extension: dataUser.hasOwnProperty('presence_id') ? dataUser.presence_id : '', + fullName: monster.util.getUserFullName(dataUser), hasFeatures: false, isAdmin: dataUser.priv_level === 'admin', showLicensedUserRoles: _.size(self.appFlags.global.servicePlansRole) > 0, @@ -210,10 +225,10 @@ define(function(require) { iconColor: 'monster-orange', title: self.i18n.active().users.hotdesk.title }, - vm_to_email: { + vmbox: { icon: 'icon-telicon-voicemail', iconColor: 'monster-green', - title: self.i18n.active().users.vm_to_email.title + title: self.i18n.active().users.vmbox.title }, faxing: { icon: 'icon-telicon-fax', @@ -273,8 +288,13 @@ define(function(require) { } } + dataUser.extra.features = _.clone(dataUser.features); + if (_vmbox) { + dataUser.extra.features.push('vmbox'); + } + dataUser.extra.countFeatures = 0; - _.each(dataUser.features, function(v) { + _.each(dataUser.extra.features, function(v) { if (v in dataUser.extra.mapFeatures) { dataUser.extra.countFeatures++; dataUser.extra.mapFeatures[v].active = true; @@ -302,7 +322,7 @@ define(function(require) { var flow = _mainCallflow.flow, module = 'user'; - if (dataUser.features.indexOf('find_me_follow_me') >= 0) { + if (dataUser.extra.features.indexOf('find_me_follow_me') >= 0) { module = 'ring_group'; dataUser.extra.groupTimeout = true; } @@ -372,14 +392,20 @@ define(function(require) { countUsers: data.users.length }, mapUsers = {}, + mapVMBoxes, registeredDevices = _.map(data.deviceStatus, function(device) { return device.device_id; }); if (_.size(self.appFlags.global.servicePlansRole) > 0) { dataTemplate.showLicensedUserRoles = true; } + mapVMBoxes = _.groupBy(data.vmboxes, 'owner_id'); + _.each(data.users, function(user) { - mapUsers[user.id] = self.usersFormatUserData(user); + mapUsers[user.id] = self.usersFormatUserData({ + user: user, + userVMBox: self.usersGetUserMainVMBox(user, mapVMBoxes[user.id]) + }); }); _.each(data.callflows, function(callflow) { @@ -508,41 +534,6 @@ define(function(require) { return result; }, - usersDeleteDialog: function(user, callback) { - var self = this, - dataTemplate = { - user: user - }, - dialogTemplate = $(self.getTemplate({ - name: 'deleteDialog', - data: dataTemplate, - submodule: 'users' - })); - - monster.ui.tooltips(dialogTemplate); - - dialogTemplate.find('#confirm_button').on('click', function() { - var removeDevices = dialogTemplate.find('#delete_devices').is(':checked'), - removeConferences = dialogTemplate.find('#delete_conferences').is(':checked'); - - self.usersDelete(user.id, removeDevices, removeConferences, function(data) { - popup.dialog('close').remove(); - - callback && callback(data); - }); - }); - - dialogTemplate.find('#cancel_button').on('click', function() { - popup.dialog('close').remove(); - }); - - var popup = monster.ui.dialog(dialogTemplate, { - title: '', - position: ['center', 20], - dialogClass: 'monster-alert' - }); - }, - usersBindEvents: function(template, parent, data) { var self = this, currentUser, @@ -693,8 +684,10 @@ define(function(require) { }); }, vmboxes: function(callback) { - self.usersListVMBoxes(function(vmboxes) { - callback(null, vmboxes); + self.usersListVMBoxes({ + success: function(vmboxes) { + callback(null, vmboxes); + } }); } }, function(err, results) { @@ -920,17 +913,20 @@ define(function(require) { template.on('click', '#delete_user', function() { var dataUser = $(this).parents('.grid-row').data(); - self.usersDeleteDialog(dataUser, function(data) { - monster.ui.toast({ - type: 'success', - message: self.getTemplate({ - name: '!' + self.i18n.active().users.toastrMessages.userDelete, - data: { - name: data.first_name + ' ' + data.last_name - } - }) - }); - self.usersRender(); + monster.pub('common.deleteSmartUser.renderPopup', { + user: dataUser, + callback: function(data) { + monster.ui.toast({ + type: 'success', + message: self.getTemplate({ + name: '!' + self.i18n.active().users.toastrMessages.userDelete, + data: { + name: data.first_name + ' ' + data.last_name + } + }) + }); + self.usersRender(); + } }); }); @@ -954,10 +950,10 @@ define(function(require) { var oldPresenceId = currentUser.presence_id, userToSave = $.extend(true, {}, currentUser, formData), - newName = userToSave.first_name + ' ' + userToSave.last_name, - oldName = currentUser.first_name + ' ' + currentUser.last_name, + newName = monster.util.getUserFullName(userToSave), + oldName = monster.util.getUserFullName(currentUser), isUserNameDifferent = newName !== oldName, - hasTimeout = userToSave.extra.ringingTimeout && userToSave.features.indexOf('find_me_follow_me') < 0, + hasTimeout = userToSave.extra.ringingTimeout && userToSave.extra.features.indexOf('find_me_follow_me') < 0, shouldUpdateTimeout = hasTimeout ? parseInt(currentUser.extra.ringingTimeout) !== parseInt(userToSave.extra.ringingTimeout) : false; monster.parallel({ @@ -1037,7 +1033,7 @@ define(function(require) { message: self.getTemplate({ name: '!' + toastrMessages.userUpdated, data: { - name: results.user.first_name + ' ' + results.user.last_name + name: monster.util.getUserFullName(results.user) } }) }); @@ -1080,7 +1076,7 @@ define(function(require) { message: self.getTemplate({ name: '!' + toastrMessages.pinUpdated, data: { - name: currentUser.first_name + ' ' + currentUser.last_name + name: monster.util.getUserFullName(currentUser) } }) }); @@ -1173,7 +1169,7 @@ define(function(require) { message: self.getTemplate({ name: '!' + toastrMessages.userUpdated, data: { - name: userData.data.first_name + ' ' + userData.data.last_name + name: monster.util.getUserFullName(userData.data) } }) }); @@ -1231,7 +1227,7 @@ define(function(require) { message: self.getTemplate({ name: '!' + toastrMessages.userUpdated, data: { - name: userData.data.first_name + ' ' + userData.data.last_name + name: monster.util.getUserFullName(userData.data) } }) }); @@ -1554,7 +1550,7 @@ define(function(require) { }); template.on('click', '.feature[data-feature="call_forward"]', function() { - if (currentUser.features.indexOf('find_me_follow_me') < 0) { + if (currentUser.extra.features.indexOf('find_me_follow_me') < 0) { var featureUser = $.extend(true, {}, currentUser); self.usersGetMainCallflow(featureUser.id, function(mainCallflow) { if (mainCallflow && 'flow' in mainCallflow) { @@ -1595,17 +1591,12 @@ define(function(require) { self.usersRenderMusicOnHold(currentUser); }); - template.on('click', '.feature[data-feature="vm_to_email"]', function() { - self.usersListVMBoxesUser(currentUser.id, function(vmboxes) { - currentUser.extra.deleteAfterNotify = true; - if (vmboxes.length > 0) { - self.usersGetVMBox(vmboxes[0].id, function(data) { - currentUser.extra.deleteAfterNotify = data.delete_after_notify; - - self.usersRenderVMToEmail(currentUser); - }); - } else { - self.usersRenderVMToEmail(currentUser); + template.on('click', '.feature[data-feature="vmbox"]', function() { + self.usersGetMainVMBoxSmartUser({ + user: currentUser, + success: function(vmbox) { + currentUser.extra.deleteAfterNotify = (vmbox && vmbox.delete_after_notify); + self.usersRenderVMBox(currentUser, vmbox); } }); }); @@ -2113,15 +2104,16 @@ define(function(require) { }); }, - usersRenderVMToEmail: function(currentUser) { + usersRenderVMBox: function(currentUser, vmbox) { var self = this, featureTemplate = $(self.getTemplate({ - name: 'feature-vm_to_email', + name: 'feature-vmbox', data: currentUser, submodule: 'users' })), switchFeature = featureTemplate.find('.switch-state'), - featureForm = featureTemplate.find('#vm_to_email_form'); + featureForm = featureTemplate.find('#vmbox_form'), + switchVmToEmail = featureForm.find('#vm_to_email_enabled'); monster.ui.validate(featureForm); @@ -2133,70 +2125,99 @@ define(function(require) { $(this).prop('checked') ? featureTemplate.find('.content').slideDown() : featureTemplate.find('.content').slideUp(); }); - featureTemplate.find('.save').on('click', function() { - var formData = monster.ui.getFormData('vm_to_email_form'), - userToSave = $.extend(true, {}, currentUser), - enabled = switchFeature.prop('checked'), - args = { - callback: function() { - popup.dialog('close').remove(); - }, - openedTab: 'features' - }, - updateUser = function(user) { - self.usersUpdateUser(user, function(data) { - args.userId = data.data.id; + switchVmToEmail.on('change', function() { + $(this).prop('checked') ? featureForm.find('.extra-content').slideDown() : featureForm.find('.extra-content').slideUp(); + }); - self.usersRender(args); - }); - }, - /* updates all the vmboxes with the new delete after notify setting, and then calls the callback*/ - updateVMsDeleteAfterNotify = function(val, userId, callbackAfterUpdate) { - self.usersListVMBoxesUser(userId, function(vmboxes) { - var listFnParallel = []; + featureTemplate.find('.save').on('click', function() { + if (!monster.ui.valid(featureForm)) { + return; + } - _.each(vmboxes, function(vm) { - listFnParallel.push(function(callback) { - self.usersGetVMBox(vm.id, function(data) { - /* Only update vms if the deleteAfterNotify value is different than before */ - if (data.delete_after_notify !== val) { - data.delete_after_notify = val; + var enabled = switchFeature.prop('checked'), + formData = monster.ui.getFormData('vmbox_form'), + userId = currentUser.id, + vmToEmailEnabled = enabled && formData.vm_to_email_enabled, + deleteAfterNotify = vmToEmailEnabled && formData.delete_after_notify; - self.usersUpdateVMBox(data, function(data) { - callback(null, data); - }); - } else { - callback(null, data); - } - }); - }); + monster.waterfall([ + function(callback) { + if (vmbox && !enabled) { + self.usersDeleteUserVMBox({ + userId: userId, + voicemailId: vmbox.id, + callback: callback }); + return; + } - monster.parallel(listFnParallel, function(err, results) { - callbackAfterUpdate && callbackAfterUpdate(results); + if (!vmbox && enabled) { + self.usersAddMainVMBoxToUser({ + user: currentUser, + deleteAfterNotify: deleteAfterNotify, + callback: callback }); - }); - }; + return; + } - userToSave.vm_to_email_enabled = enabled; + if (!vmbox || vmbox.delete_after_notify === deleteAfterNotify) { + callback(null); + return; + } - /* Only update the email and the checkboxes if the setting is enabled */ - if (enabled === true) { - if (monster.ui.valid(featureForm)) { - userToSave.email = formData.email; + self.usersPatchVMBox({ + data: { + voicemailId: vmbox.id, + data: { + delete_after_notify: deleteAfterNotify + } + }, + success: function() { + callback(null); + }, + error: function() { + callback(true); + } + }); + }, + function(callback) { + if (currentUser.vm_to_email_enabled === vmToEmailEnabled) { + callback(null); + return; + } - /* Update VMBoxes, then update user and finally close the popup */ - updateVMsDeleteAfterNotify(formData.delete_after_notify, userToSave.id, function() { - updateUser(userToSave); + self.usersPatchUser({ + data: { + userId: userId, + data: { + vm_to_email_enabled: vmToEmailEnabled + } + }, + success: function() { + callback(null); + }, + error: function() { + callback(true); + } }); } - } else { - updateUser(userToSave); - } + ], function(err) { + if (err) { + return; + } + + self.usersRender({ + userId: userId, + openedTab: 'features', + callback: function() { + popup.dialog('close').remove(); + } + }); + }); }); var popup = monster.ui.dialog(featureTemplate, { - title: currentUser.extra.mapFeatures.vm_to_email.title, + title: currentUser.extra.mapFeatures.vmbox.title, position: ['center', 20] }); }, @@ -3051,8 +3072,9 @@ define(function(require) { }, usersCleanUserData: function(userData) { - var userData = $.extend(true, {}, userData), - fullName = userData.first_name + ' ' + userData.last_name, + var self = this, + userData = $.extend(true, {}, userData), + fullName = monster.util.getUserFullName(userData), defaultCallerIdName = fullName.substring(0, 15), newCallerIDs = { caller_id: { @@ -3176,7 +3198,9 @@ define(function(require) { } }); - var dataTemplate = self.usersFormatUserData(userData), + var dataTemplate = self.usersFormatUserData({ + user: userData + }), template = $(self.getTemplate({ name: 'features', data: dataTemplate, @@ -3206,29 +3230,31 @@ define(function(require) { }); }, vmboxes: function(callback) { - self.usersListVMBoxes(function(vmboxes) { - var firstVmboxId, - results = { - listExisting: [], - userVM: {} - }; + self.usersListVMBoxes({ + success: function(vmboxes) { + var firstVmboxId, + results = { + listExisting: [], + userVM: {} + }; - _.each(vmboxes, function(vmbox) { - results.listExisting.push(vmbox.mailbox); + _.each(vmboxes, function(vmbox) { + results.listExisting.push(vmbox.mailbox); - if (vmbox.owner_id === userId && !firstVmboxId) { - firstVmboxId = vmbox.id; - } - }); + if (vmbox.owner_id === userId && !firstVmboxId) { + firstVmboxId = vmbox.id; + } + }); - if (firstVmboxId) { - self.usersGetVMBox(firstVmboxId, function(vmbox) { - results.userVM = vmbox; + if (firstVmboxId) { + self.usersGetVMBox(firstVmboxId, function(vmbox) { + results.userVM = vmbox; + callback(null, results); + }); + } else { callback(null, results); - }); - } else { - callback(null, results); + } } }); } @@ -3243,7 +3269,13 @@ define(function(require) { } }); - var dataTemplate = self.usersFormatUserData(userData, results.mainDirectory, results.mainCallflow, results.vmboxes.userVM, results.vmboxes.listExisting), + var dataTemplate = self.usersFormatUserData({ + user: userData, + userMainCallflow: results.mainCallflow, + userVMBox: results.vmboxes.userVM, + mainDirectory: results.mainDirectory, + existingVmboxes: results.vmboxes.listExisting + }), template = $(self.getTemplate({ name: 'name', data: dataTemplate, @@ -3295,17 +3327,37 @@ define(function(require) { }); }, - usersGetNumbersData: function(userId, callback, loadNumbersView) { + /** + * Get user numbers data from API + * @param {Object} args + * @param {String} args.userId User ID + * @param {Boolean} args.loadAllCallflows Indicates if all callflows should be loaded from API + * @param {Boolean} args.loadUserDevices Indicates if user devices should be loaded from API + * @param {Function} args.callback Function to be called when data has been obtained + */ + usersGetNumbersData: function(args) { var self = this, + userId = args.userId, parallelRequests = { user: function(callbackParallel) { self.usersGetUser(userId, function(user) { - callbackParallel && callbackParallel(null, user); + callbackParallel(null, user); }); }, callflow: function(callbackParallel) { var response = {}; + // If all callflows are not required, only get user main callflow + if (!args.loadAllCallflows) { + self.usersGetMainCallflow(userId, function(callflow) { + response.userCallflow = callflow; + + callbackParallel(null, response); + }); + return; + } + + // Else, get all callflows along with the main user callflow self.usersListCallflows(function(callflows) { response.list = callflows; @@ -3330,31 +3382,31 @@ define(function(require) { success: function(callflow) { response.userCallflow = callflow.data; - callbackParallel && callbackParallel(null, response); + callbackParallel(null, response); } }); } else { - callbackParallel && callbackParallel(null, response); + callbackParallel(null, response); } }); }, numbers: function(callbackParallel) { self.usersListNumbers(function(listNumbers) { - callbackParallel && callbackParallel(null, listNumbers); + callbackParallel(null, listNumbers); }); } }; - if (loadNumbersView) { + if (args.loadUserDevices) { parallelRequests.devices = function(callbackParallel) { self.usersListDeviceUser(userId, function(listDevices) { - callbackParallel && callbackParallel(null, listDevices); + callbackParallel(null, listDevices); }); }; } monster.parallel(parallelRequests, function(err, results) { - callback && callback(results); + args.hasOwnProperty('callback') && args.callback(results); }); }, @@ -3362,8 +3414,10 @@ define(function(require) { var self = this, template; - self.usersGetNumbersData(userId, function(results) { - self.usersFormatNumbersData(userId, results, function(results) { + self.usersGetFormattedNumbersData({ + userId: userId, + loadNumbersView: true, + callback: function(results) { template = $(self.getTemplate({ name: 'numbers', data: _.merge({ @@ -3388,8 +3442,8 @@ define(function(require) { }); callback && callback(template, results); - }); - }, true); + } + }); }, usersGetDevicesTemplate: function(userId, callback) { var self = this, @@ -3411,8 +3465,10 @@ define(function(require) { var self = this, template; - self.usersGetNumbersData(userId, function(results) { - self.usersFormatNumbersData(userId, results, function(results) { + self.usersGetFormattedNumbersData({ + userId: userId, + loadAllExtensions: true, + callback: function(results) { template = $(self.getTemplate({ name: 'extensions', data: results, @@ -3420,7 +3476,7 @@ define(function(require) { })); callback && callback(template, results); - }); + } }); }, usersGetLicensedRoleTemplate: function(userId, callback) { @@ -3480,8 +3536,17 @@ define(function(require) { return formattedData; }, - usersFormatNumbersData: function(userId, data, callback) { + /** + * Format user numbers data + * @param {Object} args + * @param {Object} args.data User, callflows and numbers data + * @param {Function} args.callback Function to be called when the data has been formatted + * @param {Boolean} args.includeAllExtensions Indicates if all extensions should be included in the formatted data + */ + usersFormatNumbersData: function(args) { var self = this, + data = args.data, + callback = args.callback, response = { countSpare: 0, assignedNumbers: [], @@ -3538,6 +3603,15 @@ define(function(require) { response.assignedNumbers = _.sortBy(response.assignedNumbers, 'phoneNumber'); + response.emptyAssigned = _.isEmpty(response.assignedNumbers); + response.emptySpare = _.isEmpty(response.unassignedNumbers); + response.emptyExtensions = _.isEmpty(response.extensions); + + if (!args.includeAllExtensions) { + callback && callback(response); + return; + } + /* List of extensions */ response.allExtensions = []; @@ -3563,16 +3637,12 @@ define(function(require) { return result; }); - response.emptyAssigned = _.isEmpty(response.assignedNumbers); - response.emptySpare = _.isEmpty(response.unassignedNumbers); - response.emptyExtensions = _.isEmpty(response.extensions); - callback && callback(response); }, usersFormatCreationData: function(data) { var self = this, - fullName = data.user.first_name + ' ' + data.user.last_name, + fullName = monster.util.getUserFullName(data.user), callerIdName = fullName.substring(0, 15), formattedData = { user: $.extend(true, {}, { @@ -3587,12 +3657,10 @@ define(function(require) { }, presence_id: data.callflow.extension, email: data.extra.differentEmail ? data.extra.email : data.user.username, - priv_level: 'user' + priv_level: 'user', + vm_to_email_enabled: true }, data.user), - vmbox: { - mailbox: data.callflow.extension, - name: fullName + self.appFlags.users.smartPBXVMBoxString - }, + vmbox: self.usersNewMainVMBox(data.callflow.extension, fullName), callflow: { contact_list: { exclude: false @@ -3636,6 +3704,7 @@ define(function(require) { // Remove vmbox from formatted data and user callflow delete formattedData.vmbox; delete formattedData.callflow.flow.children._; + formattedData.user.vm_to_email_enabled = false; } delete formattedData.user.extra; @@ -3644,209 +3713,33 @@ define(function(require) { }, /* Utils */ - usersDelete: function(userId, removeDevices, removeConferences, callback) { - var self = this; - - monster.parallel({ - devices: function(callback) { - self.usersListDeviceUser(userId, function(devices) { - callback(null, devices); - }); - }, - vmbox: function(callback) { - self.usersListVMBoxesUser(userId, function(data) { - callback(null, data); - }); - }, - callflows: function(callback) { - self.usersListCallflowsUser(userId, function(data) { - callback(null, data); - }); - }, - conferences: function(callback) { - self.usersListConferences(userId, function(data) { - callback(null, data); - }); - } - }, function(error, results) { - var listFnDelete = []; - - _.each(results.devices, function(device) { - listFnDelete.push(function(callback) { - if (removeDevices) { - self.usersDeleteDevice(device.id, function(data) { - callback(null, ''); - }); - } else { - self.usersUnassignDevice(device.id, function(data) { - callback(null, ''); - }); - } - }); - }); - - listFnDelete.push(function(callback) { - self.usersRemoveBulkConferences(results.conferences, removeConferences, function() { - callback(null, ''); - }); - }); - - _.each(results.callflows, function(callflow) { - /* - Special case for users with mobile devices: - reassign mobile devices to their respective mobile callflow instead of just deleting the callflow - */ - if (callflow.type === 'mobile') { - listFnDelete.push(function(mainCallback) { - monster.parallel({ - callflow: function(callback) { - self.usersGetCallflow(callflow.id, function(data) { - callback(null, data); - }); - }, - mobileDevice: function(callback) { - self.usersGetMobileDevice(callflow.numbers[0].slice(2), function(data) { - callback(null, data); - }); - } - }, function(err, results) { - var fullCallflow = results.callflow, - mobileDeviceId = results.mobileDevice.id; - - delete fullCallflow.owner_id; - - $.extend(true, fullCallflow, { - flow: { - module: 'device', - data: { - id: mobileDeviceId - } - } - }); - - self.usersUpdateCallflow(fullCallflow, function(data) { - mainCallback(null, data); - }); - }); - }); - } else { - listFnDelete.push(function(callback) { - self.usersDeleteCallflow(callflow.id, function(data) { - callback(null, ''); - }); - }); - } - }); - - _.each(results.vmbox, function(vmbox) { - listFnDelete.push(function(callback) { - self.usersDeleteVMBox(vmbox.id, function(data) { - callback(null, ''); - }); - }); - }); - - monster.parallel(listFnDelete, function(err, resultsDelete) { - self.usersDeleteUser(userId, function(data) { - callback && callback(data); - }); - }); - }); - }, - - usersGetCallflow: function(callflowId, callback) { - var self = this; - - self.callApi({ - resource: 'callflow.get', - data: { - accountId: self.accountId, - callflowId: callflowId - }, - success: function(data, status) { - callback(data.data); - } - }); - }, - - usersGetMobileDevice: function(mdn, callback) { - var self = this; - - self.callApi({ - resource: 'device.list', - data: { - accountId: self.accountId, - data: { - filters: { - 'filter_mobile.mdn': encodeURIComponent(mdn) - } - } - }, - success: function(data, status) { - callback(data.data); - } - }); - }, - - usersDeleteUser: function(userId, callback) { - var self = this; - - self.callApi({ - resource: 'user.delete', - data: { - userId: userId, - accountId: self.accountId, - data: {} - }, - success: function(data) { - callback(data.data); - } - }); - }, - usersDeleteVMBox: function(vmboxId, callback) { + /** + * Deletes a Voicemail Box by ID + * @param {Object} args + * @param {String} args.voicemailId Voicemail Box ID + * @param {Function} args.success Success callback + * @param {Function} args.error Error callback + */ + usersDeleteVMBox: function(args) { var self = this; self.callApi({ resource: 'voicemail.delete', data: { - voicemailId: vmboxId, + voicemailId: args.voicemailId, accountId: self.accountId, data: {} }, success: function(data) { - callback(data.data); - } - }); - }, - - usersUnassignDevice: function(deviceId, callback) { - var self = this; - - self.usersGetDevice(deviceId, function(deviceGet) { - delete deviceGet.owner_id; - - self.usersUpdateDevice(deviceGet, function(updatedDevice) { - callback && callback(updatedDevice); - }); - }); - }, - - usersDeleteDevice: function(deviceId, callback) { - var self = this; - - self.callApi({ - resource: 'device.delete', - data: { - deviceId: deviceId, - accountId: self.accountId, - data: {} + args.hasOwnProperty('success') && args.success(data.data); }, - success: function(data) { - callback(data.data); + error: function(parsedError) { + args.hasOwnProperty('error') && args.error(parsedError); } }); }, + usersDeleteCallflow: function(callflowId, callback) { var self = this; @@ -3891,9 +3784,14 @@ define(function(require) { } data.vmbox.owner_id = _dataUser.id; - self.usersCreateVMBox(data.vmbox, function(_dataVM) { - data.callflow.flow.children._.data.id = _dataVM.id; - callback(null, _dataUser); + self.usersCreateVMBox({ + data: { + data: data.vmbox + }, + success: function(_dataVM) { + data.callflow.flow.children._.data.id = _dataVM.id; + callback(null, _dataUser); + } }); }, function(_dataUser, callback) { @@ -3937,7 +3835,7 @@ define(function(require) { } }; - var fullName = user.first_name + ' ' + user.last_name, + var fullName = monster.util.getUserFullName(user), callflow = { contact_list: { exclude: false @@ -4129,7 +4027,7 @@ define(function(require) { var indexMain = -1; _.each(listCallflows, function(callflow, index) { - if (callflow.owner_id === userId && callflow.type === 'mainUserCallflow' || !('type' in callflow)) { + if (callflow.type === 'mainUserCallflow' || !('type' in callflow)) { indexMain = index; return false; } @@ -4274,19 +4172,29 @@ define(function(require) { }); }, - usersListVMBoxes: function(callback) { + /** + * Gets the list of vmboxes + * @param {Object} args + * @param {Object} args.data Data to be used by the SDK to query the API + * @param {Function} args.success Success callback + * @param {Function} args.error Error callback + */ + usersListVMBoxes: function(args) { var self = this; self.callApi({ resource: 'voicemail.list', - data: { + data: _.merge({ accountId: self.accountId, filters: { paginate: 'false' } - }, + }, args.data), success: function(data) { - callback(data.data); + args.hasOwnProperty('success') && args.success(data.data); + }, + error: function(parsedError) { + args.hasOwnProperty('error') && args.error(parsedError); } }); }, @@ -4294,18 +4202,13 @@ define(function(require) { usersListVMBoxesUser: function(userId, callback) { var self = this; - self.callApi({ - resource: 'voicemail.list', + self.usersListVMBoxes({ data: { - accountId: self.accountId, filters: { - filter_owner_id: userId, - paginate: 'false' + filter_owner_id: userId } }, - success: function(data) { - callback(data.data); - } + success: callback }); }, @@ -4324,17 +4227,27 @@ define(function(require) { }); }, - usersCreateVMBox: function(vmData, callback) { + /** + * Creates a Voicemail Box + * @param {Object} args + * @param {Object} args.data Data to be sent by the SDK to the API + * @param {Object} args.data.data Data provided for the Voicemail Box to be created + * @param {Function} args.success Success callback + * @param {Function} args.error Error callback + */ + usersCreateVMBox: function(args) { var self = this; self.callApi({ resource: 'voicemail.create', - data: { - accountId: self.accountId, - data: vmData - }, + data: _.merge({ + accountId: self.accountId + }, args.data), success: function(data) { - callback(data.data); + args.hasOwnProperty('success') && args.success(data.data); + }, + error: function(parsedError) { + args.hasOwnProperty('error') && args.error(parsedError); } }); }, @@ -4660,6 +4573,18 @@ define(function(require) { callback(null, data.data); } }); + }, + vmboxes: function(callback) { + self.usersListVMBoxes({ + data: { + filters: { + 'filter_ui_metadata.origin': 'voip' + } + }, + success: function(vmboxes) { + callback(null, vmboxes); + } + }); } }, function(err, results) { callback && callback(results); @@ -4926,7 +4851,7 @@ define(function(require) { }); }, function(vmbox, wfCallback) { - vmbox.name = user.first_name + ' ' + user.last_name + self.appFlags.users.smartPBXVMBoxString; + vmbox.name = self.usersGetMainVMBoxName(monster.util.getUserFullName(user)); // We only want to update the vmbox number if it was already synced with the presenceId (and if the presenceId was not already set) // This allows us to support old clients who have mailbox number != than their extension number if (oldPresenceId === vmbox.mailbox) { @@ -4950,7 +4875,7 @@ define(function(require) { monster.parallel({ conference: function(callback) { var baseConference = { - name: data.user.first_name + ' ' + data.user.last_name + self.appFlags.users.smartPBXConferenceString, + name: monster.util.getUserFullName(data.user) + self.appFlags.users.smartPBXConferenceString, owner_id: data.user.id, play_name_on_join: true, member: { @@ -5280,6 +5205,355 @@ define(function(require) { args.hasOwnProperty('onChargesCancelled') && args.onChargesCancelled(); } }); + }, + + /** + * Deletes user's main Voicemail Box, and removes it from the main user callflow + * @param {Object} args + * @param {String} args.userId User ID + * @param {String} args.voicemailId ID of the voicemail to delete + * @param {Function} args.callback Callback for monster.waterfall + */ + usersDeleteUserVMBox: function(args) { + var self = this, + voicemailId = args.voicemailId; + + monster.waterfall([ + function(waterfallCallback) { + self.usersGetMainCallflow(args.userId, function(mainUserCallflow) { + waterfallCallback(null, mainUserCallflow); + }); + }, + function(mainUserCallflow, waterfallCallback) { + if (_.isNil(mainUserCallflow)) { + waterfallCallback(null); + return; + } + + var flowChildren = mainUserCallflow.flow.children; + + // Check if main user callflow has been created via the voip app, and it has + // the voicemail set in the default position. If it is not the case, do not + // modify. + if (mainUserCallflow.ui_metadata.origin !== 'voip' + || _.isEmpty(flowChildren) + || flowChildren._.module !== 'voicemail' + || flowChildren._.data.id !== voicemailId) { + waterfallCallback(null); + return; + } + + // Remove voicemail from callflow, and save + mainUserCallflow.flow.children = {}; + + self.usersUpdateCallflow(mainUserCallflow, function() { + waterfallCallback(null); + }); + }, + function(waterfallCallback) { + self.usersDeleteVMBox({ + voicemailId: args.voicemailId, + success: function() { + waterfallCallback(null); + }, + error: function() { + waterfallCallback(true); + } + }); + } + ], function(err, result) { + if (err) { + args.hasOwnProperty('callback') && args.callback(err); + return; + } + + args.hasOwnProperty('callback') && args.callback(null); + }); + }, + + /** + * Adds a main VMBox to an existing user + * @param {Object} args + * @param {String} args.user User + * @param {Boolean} args.deleteAfterNotify Delete after notify voicemail box flag + * @param {Function} args.callback Callback for monster.waterfall + */ + usersAddMainVMBoxToUser: function(args) { + var self = this, + user = args.user, + userId = user.id; + + monster.waterfall([ + function(waterfallCallback) { + // If user has presence_id, there is no need to get all numbers data, only main callflow + if (user.presence_id) { + self.usersGetMainCallflow(userId, function(mainCallflow) { + waterfallCallback(null, { + user: user, + callflow: mainCallflow + }); + }); + return; + } + + // Otherwise, get user's numbers data + self.usersGetFormattedNumbersData({ + userId: userId, + callback: function(userNumbersData) { + waterfallCallback(null, userNumbersData); + } + }); + }, + function(userData, waterfallCallback) { + // Create voicemail box + var user = userData.user, + userFullName = monster.util.getUserFullName(user), + mailbox = user.presence_id || _.head(userData.extensions); + + if (_.isNil(mailbox)) { + // There is no extension to set for the mailbox + waterfallCallback(true); + return; + } + + self.usersCreateVMBox({ + data: { + data: self.usersNewMainVMBox(mailbox, userFullName, userId, args.deleteAfterNotify) + }, + success: function(userVMBox) { + waterfallCallback(null, userData, userVMBox); + } + }); + }, + function(userData, userVMBox, waterfallCallback) { + var mainUserCallflow = userData.callflow; + + // Do not update main callflow if it does not has + // been created by the voip app, or if does not have + // empty children at the root of the flow, which + // is the default main user callflow without vmbox + if (mainUserCallflow.ui_metadata.origin !== 'voip' || !_.isEmpty(mainUserCallflow.flow.children)) { + waterfallCallback(null); + return; + } + + // Otherwise, add vmbox to callflow + mainUserCallflow.flow.children._ = { + children: {}, + data: { + id: userVMBox.id + }, + module: 'voicemail' + }; + + self.usersUpdateCallflow(mainUserCallflow, function() { + waterfallCallback(null); + }); + } + ], function(err, result) { + if (err) { + args.hasOwnProperty('callback') && args.callback(err); + return; + } + + args.hasOwnProperty('callback') && args.callback(null); + }); + }, + + /** + * Gets the main voicemail box for a user, which has been created through Smart PBX app + * @param {Object} args + * @param {Object} args.user User data + * @param {Function} args.success Success callback + * @param {Function} args.error Error callback + */ + usersGetMainVMBoxSmartUser: function(args) { + var self = this, + user = args.user; + + monster.waterfall([ + function(callback) { + self.usersListVMBoxesSmartUser({ + userId: user.id, + success: function(vmboxes) { + callback(null, self.usersGetUserMainVMBox(user, vmboxes)); + }, + error: function(parsedError) { + callback(parsedError); + } + }); + }, + function(vmboxLite, callback) { + if (!vmboxLite) { + callback(null, null); + return; + } + + self.usersGetVMBox(vmboxLite.id, function(vmbox) { + callback(null, vmbox); + }); + } + ], function(err, vmbox) { + if (err) { + args.hasOwnProperty('error') && args.error(err); + return; + } + + args.hasOwnProperty('success') && args.success(vmbox); + }); + }, + + /** + * Gets the numbers data assigned to a user, separated in phone numbers and extensions + * @param {Object} args + * @param {String} args.userId User ID + * @param {Boolean} args.loadAllExtensions Indicates if all extensions should be loaded from API + * @param {Boolean} args.loadNumbersView Indicates if the numbers view should be loaded + * @param {Function} args.callback Function to be called when the numbers data has been obtained and formatted + */ + usersGetFormattedNumbersData: function(args) { + var self = this, + loadAllExtensions = !!args.loadAllExtensions, + callback = args.callback; + + self.usersGetNumbersData({ + userId: args.userId, + loadUserDevices: !!args.loadNumbersView, + loadAllCallflows: loadAllExtensions, + callback: function(results) { + self.usersFormatNumbersData({ + data: results, + includeAllExtensions: loadAllExtensions, + callback: callback + }); + } + }); + }, + + /** + * Gets the list of vmboxes for a user, that has been created through Smart PBX app + * @param {Object} args + * @param {String} args.userId User ID + * @param {Function} args.success Success callback + * @param {Function} args.error Error callback + */ + usersListVMBoxesSmartUser: function(args) { + var self = this; + + self.usersListVMBoxes({ + data: { + filters: { + filter_owner_id: args.userId, + 'filter_ui_metadata.origin': 'voip' + } + }, + success: args.success + }); + }, + + /** + * Update specific values of a user + * @param {Object} args + * @param {Object} args.data Data to be sent by the SDK to the API + * @param {String} args.data.userId ID of the user to be patched + * @param {Object} args.data.data User data to be patched + * @param {Function} args.success Success callback + * @param {Function} args.error Error callback + */ + usersPatchUser: function(args) { + var self = this; + + self.callApi({ + resource: 'user.patch', + data: _.merge({ + accountId: self.accountId + }, args.data), + success: function(data, status) { + args.hasOwnProperty('success') && args.success(data.data); + }, + error: function(parsedError) { + args.hasOwnProperty('error') && args.error(parsedError); + } + }); + }, + + /** + * Update specific values of a voicemail box + * @param {Object} args + * @param {Object} args.data Data to be sent by the SDK to the API + * @param {String} args.data.voicemailId ID of the voicemail box to be patched + * @param {Object} args.data.data Voicemail box data to be patched + * @param {Function} args.success Success callback + * @param {Function} args.error Error callback + */ + usersPatchVMBox: function(args) { + var self = this; + + self.callApi({ + resource: 'voicemail.patch', + data: _.merge({ + accountId: self.accountId + }, args.data), + success: function(data, status) { + args.hasOwnProperty('success') && args.success(data.data); + }, + error: function(parsedError) { + args.hasOwnProperty('error') && args.error(parsedError); + } + }); + }, + + /** + * Get the main VMBox for a user, from a list of voicemail boxes, which are assumed to + * belong to it already + * @param {Object} user User data + * @param {Array} vmboxes List of voicemail boxes that belong to the user + */ + usersGetUserMainVMBox: function(user, vmboxes) { + if (_.isEmpty(vmboxes)) { + return null; + } + + var presenceId = user.presence_id ? user.presence_id.toString() : null, + mainUserVMBox = _.find(vmboxes, function(vmbox) { + return vmbox.mailbox === presenceId; + }); + + if (mainUserVMBox) { + return mainUserVMBox; + } + + return _.head(vmboxes); + }, + + /** + * Gets a new Voicemail Box object + * @param {Number} mailbox Mailbox + * @param {String} userName User full name + * @param {String} [userId] User ID + * @param {Boolean} [deleteAfterNotify] Delete voicemail message after notify user + * @returns {Object} Voicemail Box object + */ + usersNewMainVMBox: function(mailbox, userName, userId, deleteAfterNotify) { + var self = this; + + return { + owner_id: userId, + mailbox: mailbox.toString(), // Force to string + name: self.usersGetMainVMBoxName(userName), + delete_after_notify: deleteAfterNotify + }; + }, + + /** + * Builds the name for the user's main voicemail box + * @param {String} userName User full name + * @returns {String} Name for the user's main voicemail box + */ + usersGetMainVMBoxName: function(userName) { + var self = this; + + return userName + self.appFlags.users.smartPBXVMBoxString; } }; diff --git a/submodules/users/users.scss b/submodules/users/users.scss index fb5b682..7001d28 100644 --- a/submodules/users/users.scss +++ b/submodules/users/users.scss @@ -759,11 +759,6 @@ margin-top: 10px; } -/**** CSS for delete user dialog ****/ -.delete-user-wrapper .dialog-text { - margin-top: 10px; -} - .monster-feature-popup-container { .feature-popup-title { display: flex; @@ -960,9 +955,11 @@ } } - &[data-feature="vm_to_email"] { - #email { - margin-top: 8px; + &[data-feature="vmbox"] .content { + padding-bottom: 0px; + + form { + margin-bottom: 0px; } .main-line { @@ -970,5 +967,33 @@ line-height: 20px; margin: 10px 0 30px 0; } + + .control-group { + &.main{ + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + padding-bottom: 15px; + + label.title { + font-size: 1em; + padding-right: 1em; + margin-bottom: 0px; + } + } + + &.extra { + margin-bottom: 40px; + } + } + + .extra-content { + padding-bottom: 15px; + + &.disabled { + display: none; + } + } } } diff --git a/submodules/users/views/deleteDialog.html b/submodules/users/views/deleteDialog.html deleted file mode 100644 index 07f60e0..0000000 --- a/submodules/users/views/deleteDialog.html +++ /dev/null @@ -1,34 +0,0 @@ -
-
-
-
{{ replaceVar i18n.users.deleteDialog.deleteUserName user.name }}
- -
- {{#monsterCheckbox i18n.users.deleteDialog.deleteDevices }} - - {{/monsterCheckbox}} - -
- -
- {{#monsterCheckbox i18n.users.deleteDialog.deleteConferences }} - - {{/monsterCheckbox}} - -
- -
- {{#compare user.priv_level "===" "admin"}} - {{ i18n.users.deleteDialog.headerAdmin }} - {{else}} - {{ i18n.users.deleteDialog.headerUser }} - {{/compare}} -
-
- -
- - -
-
-
\ No newline at end of file diff --git a/submodules/users/views/feature-vm_to_email.html b/submodules/users/views/feature-vm_to_email.html deleted file mode 100644 index 01dfa83..0000000 --- a/submodules/users/views/feature-vm_to_email.html +++ /dev/null @@ -1,42 +0,0 @@ -
-
- - -
- -
-
-
- {{replaceVar i18n.users.vm_to_email.label email}} -
- - -
- - {{#monsterText}} - {{i18n.users.vm_to_email.help}} - {{/monsterText}} -
- -
-
- {{ i18n.cancel }} - -
-
-
diff --git a/submodules/users/views/feature-vmbox.html b/submodules/users/views/feature-vmbox.html new file mode 100644 index 0000000..5cede35 --- /dev/null +++ b/submodules/users/views/feature-vmbox.html @@ -0,0 +1,55 @@ +
+
+ + +
+ +
+
+ +
+ {{i18n.users.vmbox.label}} +
+ +
+ + {{#monsterSwitch}} + + {{/monsterSwitch}} +
+ +
+
+ +
+ + {{#monsterText}} + {{replaceVar i18n.users.vmbox.vmToEmailHelp email}} + {{/monsterText}} +
+ +
+
+ +
+
+ {{ i18n.cancel }} + +
+
+
diff --git a/submodules/users/views/row.html b/submodules/users/views/row.html index a00c2ab..cd7f3bf 100644 --- a/submodules/users/views/row.html +++ b/submodules/users/views/row.html @@ -1,6 +1,6 @@ -
+
-
{{ first_name }} {{ last_name }}
+
{{ extra.fullName }}
{{#if extra.showLicensedUserRoles}}