diff --git a/app.js b/app.js index 2eb1891..f96bc65 100644 --- a/app.js +++ b/app.js @@ -194,6 +194,127 @@ define(function(require) { .find('.grid-cell.active, .grid-row.active') .removeClass('active'); }); + }, + + patchCallflow: function(args) { + var self = this; + + self.callApi({ + resource: 'callflow.patch', + data: _.merge({ + accountId: self.accountId + }, args.data), + success: function(data) { + _.has(args, 'success') && args.success(data.data); + }, + error: function(parsedError) { + _.has(args, 'error') && args.error(parsedError); + } + }); + }, + + /** + * Runs tasks necessary for mobile device callfow update on un\assignement. + * @param {String|null} userId + * @param {String|null|undefined} userMainCallflowId + * @param {Object} device + * @param {String} device.id + * @param {String} device.mobile.mdn + * @param {Function} mainCallback + * + * updateMobileCallflowAssignment(userId, userMainCallflowId|undefined, ...) + * this signature will assign the device + * + * updateMobileCallflowAssignment(null, null, ...) + * this signature will unassign the device + * + * While assigning, you can either provide the user's main callflow's ID or set it to + * `undefined`, in which case the method will take care of resolving it based on `userId`. + */ + updateMobileCallflowAssignment: function(userId, userMainCallflowId, device, mainCallback) { + var self = this, + getMainUserCallflowId = function getMainUserCallflowId(userId, callback) { + self.callApi({ + resource: 'callflow.list', + data: { + accountId: self.accountId, + filters: { + filter_owner_id: userId, + filter_type: 'mainUserCallflow' + } + }, + success: _.flow( + _.partial(_.get, _, 'data'), + _.head, + _.partial(_.get, _, 'id'), + _.partial(callback, null) + ), + error: _.partial(callback, true) + }); + }, + maybeGetMainUserCallflowId = function maybeGetMainUserCallflowId(userId, userMainCallflowId, callback) { + if (_.isNull(userMainCallflowId) || !_.isUndefined(userMainCallflowId)) { + return callback(null, userMainCallflowId); + } + getMainUserCallflowId(userId, callback); + }, + getMobileCallflowIdByNumber = function getMobileCallflowIdByNumber(number, callback) { + self.callApi({ + resource: 'callflow.searchByNumber', + data: { + accountId: self.accountId, + value: number + }, + success: _.flow( + _.partial(_.get, _, 'data'), + _.head, + _.partial(_.get, _, 'id'), + _.partial(callback, null) + ), + error: _.partial(callback, true) + }); + }, + getCallflowIds = function getCallflowIds(userId, userMainCallflowId, number, callback) { + monster.parallel({ + userMainCallflowId: _.partial(maybeGetMainUserCallflowId, userId, userMainCallflowId), + mobileCallflowId: _.partial(getMobileCallflowIdByNumber, number) + }, callback); + }, + updateMobileCallflowAssignment = function updateMobileCallflowAssignment(userId, deviceId, callflowIds, callback) { + var userMainCallflowId = callflowIds.userMainCallflowId, + mobileCallflowId = callflowIds.mobileCallflowId, + updatedCallflow = _.merge({ + owner_id: userId + }, _.isNull(userMainCallflowId) ? { + flow: { + module: 'device', + data: { + id: deviceId + } + } + } : { + flow: { + module: 'callflow', + data: { + id: userMainCallflowId + } + } + }); + + self.patchCallflow({ + data: { + callflowId: mobileCallflowId, + data: updatedCallflow + }, + success: _.partial(callback, null), + error: _.partial(callback, true) + }); + }; + + monster.waterfall([ + _.partial(getCallflowIds, userId, userMainCallflowId, device.mobile.mdn), + _.partial(updateMobileCallflowAssignment, userId, device.id) + ], mainCallback); } }; diff --git a/submodules/devices/devices.js b/submodules/devices/devices.js index f61a76f..bc65d58 100644 --- a/submodules/devices/devices.js +++ b/submodules/devices/devices.js @@ -591,7 +591,7 @@ define(function(require) { $this.prop('disabled', 'disabled'); - self.devicesSaveDevice(dataToSave, function(data) { + self.devicesSaveDevice(data.owner_id, dataToSave, function(data) { if (hasToRestart) { self.devicesRestart(data.id, function() { monster.ui.toast({ @@ -1451,18 +1451,45 @@ define(function(require) { }, /** + * @param {String|undefined} originalUserId * @param {Object} deviceData * @param {Function} callbackSuccess * @param {Function} [callbackError] */ - devicesSaveDevice: function(deviceData, callbackSuccess, callbackError) { - var self = this; + devicesSaveDevice: function(originalUserId, deviceData, callbackSuccess, callbackError) { + var self = this, + isMobileDevice = deviceData.device_type === 'mobile', + hasDifferentUserId = originalUserId !== deviceData.owner_id, + shouldUpdateMobileCallflow = isMobileDevice && hasDifferentUserId, + maybeUpdateMobileCallflowAssignment = function maybeUpdateMobileCallflowAssignment(shouldUpdateMobileCallflow, device, callback) { + if (!shouldUpdateMobileCallflow) { + return callback(null); + } + var userId = _.get(device, 'owner_id', null), + userMainCallflowId = userId ? undefined : null; - if (deviceData.id) { - self.devicesUpdateDevice(deviceData, callbackSuccess, callbackError); - } else { - self.devicesCreateDevice(deviceData, callbackSuccess, callbackError); - } + self.updateMobileCallflowAssignment(userId, userMainCallflowId, device, callback); + }, + saveDevice = function saveDevice(device, callback) { + var method = _.has(device, 'id') ? 'devicesUpdateDevice' : 'devicesCreateDevice'; + + self[method](device, _.partial(callback, null), callback); + }; + + /** + * We perform both operations in parallel because, although app#updateMobileCallflowAssignment + * requires an existing device to run, since it is not possible to create mobile devices + * from smartpbx, that ID will always be present. + */ + monster.parallel({ + _: _.partial(maybeUpdateMobileCallflowAssignment, shouldUpdateMobileCallflow, deviceData), + device: _.partial(saveDevice, deviceData) + }, function(err, results) { + if (err) { + return callbackError && callbackError(err); + } + callbackSuccess && callbackSuccess(results.device); + }); }, /** diff --git a/submodules/users/users.js b/submodules/users/users.js index fe25a5d..1f8305d 100644 --- a/submodules/users/users.js +++ b/submodules/users/users.js @@ -4728,37 +4728,6 @@ define(function(require) { }); }, - usersGetDevice: function(deviceId, callback) { - var self = this; - - self.callApi({ - resource: 'device.get', - data: { - accountId: self.accountId, - deviceId: deviceId - }, - success: function(data) { - callback(data.data); - } - }); - }, - - usersUpdateDevice: function(data, callback) { - var self = this; - - self.callApi({ - resource: 'device.update', - data: { - accountId: self.accountId, - data: data, - deviceId: data.id - }, - success: function(data) { - callback(data.data); - } - }); - }, - usersListDeviceUser: function(userId, callback) { var self = this; @@ -4838,146 +4807,146 @@ define(function(require) { }); }, - usersUpdateDevices: function(data, userId, callbackAfterUpdate) { + /** + * @param {Object} data + * @param {String[]} data.newDevices List of device IDs to assign + * @param {String[]} data.oldDevices List of device IDs to unassign + * @param {String} userId + * @param {Function} callback + */ + usersUpdateDevices: function(data, userId, callback) { var self = this, - updateDevices = function(userCallflow) { - var listFnParallel = [], - updateDeviceRequest = function(newDataDevice, callback) { - self.usersUpdateDevice(newDataDevice, function(updatedDataDevice) { - callback(null, updatedDataDevice); - }); - }; - - _.each(data.newDevices, function(deviceId) { - listFnParallel.push(function(callback) { - self.usersGetDevice(deviceId, function(data) { - data.owner_id = userId; - - if (data.device_type === 'mobile') { - self.usersSearchMobileCallflowsByNumber(userId, data.mobile.mdn, function(listCallflowData) { - self.callApi({ - resource: 'callflow.get', - data: { - accountId: self.accountId, - callflowId: listCallflowData.id - }, - success: function(rawCallflowData, status) { - var callflowData = rawCallflowData.data; - - if (userCallflow) { - $.extend(true, callflowData, { - owner_id: userId, - flow: { - module: 'callflow', - data: { - id: userCallflow.id - } - } - }); - } else { - $.extend(true, callflowData, { - owner_id: userId, - flow: { - module: 'device', - data: { - id: deviceId - } - } - }); - } - - self.usersUpdateCallflow(callflowData, function() { - updateDeviceRequest(data, callback); - }); - } - }); - }); - } else { - updateDeviceRequest(data, callback); + getUserMainCallflow = function getUserMainCallflow(userId, next) { + self.usersGetMainCallflow(userId, _.partial(next, null)); + }, + assignDeviceFactory = function assignDeviceFactory(userId, userMainCallflowId, deviceId) { + return _.bind(self.usersUpdateDeviceAssignmentFromUser, self, deviceId, userId, userMainCallflowId); + }, + unassignDeviceFactory = function unassignDeviceFactory(deviceId) { + return _.bind(self.usersUpdateDeviceAssignmentFromUser, self, deviceId, null, null); + }, + updateCallflowEndpoints = function updateCallflowEndpoints(updatedEndpoints, callflowId, next) { + self.patchCallflow({ + data: { + callflowId: callflowId, + data: { + flow: _.isEmpty(updatedEndpoints) ? { + module: 'user', + data: { + can_call_self: false, + endpoints: null, + id: userId, + timeout: 20 + } + } : { + data: { + endpoints: updatedEndpoints + } } - }); - }); + } + }, + success: _.partial(next, null), + error: _.partial(next, true) }); - - _.each(data.oldDevices, function(deviceId) { - listFnParallel.push(function(callback) { - self.usersGetDevice(deviceId, function(data) { - delete data.owner_id; - - if (data.device_type === 'mobile') { - self.usersSearchMobileCallflowsByNumber(userId, data.mobile.mdn, function(listCallflowData) { - self.callApi({ - resource: 'callflow.get', - data: { - accountId: self.accountId, - callflowId: listCallflowData.id - }, - success: function(rawCallflowData, status) { - var callflowData = rawCallflowData.data; - - delete callflowData.owner_id; - $.extend(true, callflowData, { - flow: { - module: 'device', - data: { - id: deviceId - } - } - }); - - self.usersUpdateCallflow(callflowData, function() { - updateDeviceRequest(data, callback); - }); - } - }); - }); - } else { - updateDeviceRequest(data, callback); + }, + disableFindMeFollowMeForUserId = function disableFindMeFollowMeForUserId(userId, next) { + self.usersPatchUser({ + data: { + userId: userId, + data: { + smartpbx: { + find_me_follow_me: false } - }); - }); + } + }, + success: _.partial(next, null), + error: _.partial(next, true) }); + }, + maybeUpdateUserAndCallflow = function maybeUpdateUserAndCallflow(userId, userCallflow, deviceIdsRemoved, next) { + var hasRingGroupModule = _.get(userCallflow, 'flow.module') === 'ring_group', + currentEndpoints = _.get(userCallflow, 'flow.data.endpoints', []), + updatedEndpoints = _.reject(currentEndpoints, _.flow( + _.partial(_.get, _, 'id'), + _.partial(_.includes, deviceIdsRemoved) + )), + whereEndpointsRemoved = _.size(updatedEndpoints) < _.size(currentEndpoints); + + monster.parallel(_.flatten([ + hasRingGroupModule && whereEndpointsRemoved ? [ + _.partial(updateCallflowEndpoints, updatedEndpoints, userCallflow.id) + ] : [], + hasRingGroupModule && whereEndpointsRemoved && _.isEmpty(updatedEndpoints) ? [ + _.partial(disableFindMeFollowMeForUserId, userId) + ] : [] + ]), next); + }, + updateEntities = function updateEntities(userId, devices, userMainCallflow, next) { + monster.parallel(_.flatten([ + _.map(data.newDevices, _.partial(assignDeviceFactory, userId, _.get(userMainCallflow, 'id'))), + _.map(data.oldDevices, unassignDeviceFactory), + _.partial(maybeUpdateUserAndCallflow, userId, userMainCallflow, data.oldDevices) + ]), next); + }; - if (data.oldDevices.length > 0 && userCallflow && userCallflow.flow.module === 'ring_group') { - var endpointsCount = userCallflow.flow.data.endpoints.length; - - userCallflow.flow.data.endpoints = _.filter(userCallflow.flow.data.endpoints, function(endpoint) { - return (data.oldDevices.indexOf(endpoint.id) < 0); - }); + monster.waterfall([ + _.partial(getUserMainCallflow, userId), + _.partial(updateEntities, userId, data) + ], callback); + }, - if (userCallflow.flow.data.endpoints.length < endpointsCount) { - if (userCallflow.flow.data.endpoints.length === 0) { - userCallflow.flow.module = 'user'; - userCallflow.flow.data = { - can_call_self: false, - id: userId, - timeout: '20' - }; - listFnParallel.push(function(callback) { - self.usersGetUser(userId, function(user) { - user.smartpbx.find_me_follow_me.enabled = false; - self.usersUpdateUser(user, function(data) { - callback(null, data); - }); - }); - }); - } - listFnParallel.push(function(callback) { - self.usersUpdateCallflow(userCallflow, function(data) { - callback(null, data); - }); - }); - } + usersUpdateDeviceAssignmentFromUser: function(deviceId, userId, userMainCallflowId, mainCallback) { + var self = this, + getDevice = function getDevice(deviceId, callback) { + self.callApi({ + resource: 'device.get', + data: { + accountId: self.accountId, + deviceId: deviceId + }, + success: _.flow( + _.partial(_.get, _, 'data'), + _.partial(callback, null) + ), + error: _.partial(callback, true) + }); + }, + patchDevice = function patchDevice(data, deviceId, callback) { + self.callApi({ + resource: 'device.patch', + data: { + accountId: self.accountId, + deviceId: deviceId, + data: data + }, + success: _.flow( + _.partial(_.get, _, 'data'), + _.partial(callback, null) + ), + error: _.partial(callback, true) + }); + }, + maybeUpdateMobileCallflowAssignment = function maybeUpdateMobileCallflowAssignment(userId, userMainCallflowId, device, callback) { + if (device.device_type !== 'mobile') { + return callback(null); } + self.updateMobileCallflowAssignment(userId, userMainCallflowId, device, callback); + }, + updateDeviceAssignment = function updateDeviceAssignment(userId, userMainCallflowId, device, callback) { + var updatedDevice = { + owner_id: userId + }; - monster.parallel(listFnParallel, function(err, results) { - callbackAfterUpdate && callbackAfterUpdate(results); - }); + monster.parallel([ + _.partial(maybeUpdateMobileCallflowAssignment, userId, userMainCallflowId, device), + _.partial(patchDevice, updatedDevice, device.id) + ], callback); }; - self.usersGetMainCallflow(userId, function(callflow) { - updateDevices(callflow); - }); + monster.waterfall([ + _.partial(getDevice, deviceId), + _.partial(updateDeviceAssignment, userId, userMainCallflowId) + ], mainCallback); }, usersUpdateCallflowNumbers: function(userId, callflowId, numbers, callback) {