define(function(require) { var $ = require('jquery'), _ = require('lodash'), monster = require('monster'), timezone = require('monster-timezone'); var app = { requests: { }, subscribe: { 'voip.strategy.render': 'strategyRender', 'auth.currentAccountUpdated': '_strategyOnCurrentAccountUpdated' }, weekdays: [ 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday' ], weekdayLabels: [ 'MainMonday', 'MainTuesday', 'MainWednesday', 'MainThursday', 'MainFriday', 'MainSaturday', 'MainSunday' ], subCallflowsLabel: [ 'MainOpenHours', 'MainAfterHours', 'MainLunchHours', 'MainHolidays' ], featureCodeConfigs: [ { name: 'directed_ext_pickup', number: '87', pattern: '^\\*87([0-9]+)$', moduleName: 'group_pickup_feature', metadata: { type: 'extension' } }, { name: 'call_forward[action=deactivate]', number: '73', callflowNumber: '*73', moduleName: 'call_forward', actionName: 'deactivate' }, { name: 'call_forward[action=activate]', number: '72', pattern: '\\*72([0-9]*)$', moduleName: 'call_forward', actionName: 'activate' }, { name: 'call_forward[action=toggle]', number: '74', pattern: '^\\*74([0-9]*)$', moduleName: 'call_forward', actionName: 'toggle' }, { name: 'call_forward[action=update]', number: '56', callflowNumber: '*56', moduleName: 'call_forward', actionName: 'update' }, { name: 'hotdesk[action=login]', number: '11', callflowNumber: '*11', moduleName: 'hotdesk', actionName: 'login' }, { name: 'hotdesk[action=logout]', number: '12', callflowNumber: '*12', moduleName: 'hotdesk', actionName: 'logout' }, { name: 'hotdesk[action=toggle]', number: '13', callflowNumber: '*13', moduleName: 'hotdesk', actionName: 'toggle' }, { name: 'voicemail[action=check]', number: '97', pattern: '^\\*97([0-9]*)$', moduleName: 'voicemail', actionName: 'check' }, { name: 'voicemail[single_mailbox_login]', number: '98', pattern: '^\\*98([0-9]*)$', moduleName: 'voicemail', actionName: 'check', metadata: { single_mailbox_login: true } }, { name: 'voicemail[action="direct"]', number: '*', pattern: '^\\*\\*([0-9]*)$', moduleName: 'voicemail', actionName: 'compose' }, { name: 'intercom', number: '0', pattern: '^\\*0([0-9]*)$', moduleName: 'intercom', actionName: 'compose' }, { name: 'privacy[mode=full]', number: '67', pattern: '^\\*67([0-9]*)$', moduleName: 'privacy', actionName: 'full' }, { name: 'park_and_retrieve', number: '3', pattern: '^\\*3([0-9]*)$', moduleName: 'park', actionName: 'auto' }, { name: 'valet', number: '4', callflowNumber: '*4', moduleName: 'park', actionName: 'park' }, { name: 'retrieve', number: '5', pattern: '^\\*5([0-9]*)$', moduleName: 'park', actionName: 'retrieve' }, { name: 'move', number: '6683', callflowNumber: '6683', moduleName: 'move' } ], /** * Render main layout * @param {Object} args * @param {jQuery} args.parent Parent template * @param {String} [args.openElement] Name of the element to display on render * @param {Function} [args.callback] Callback to execute after render */ strategyRender: function(args) { var self = this, args = args || {}, parent = args.parent || $('.right-content'), openElement = args.openElement, callback = args.callback; monster.parallel({ temporalRules: function(callback) { self.strategyGetTemporalRules(function(temporalRules) { callback(null, temporalRules); }); }, callflows: function(callback) { self.strategyGetMainCallflows(function(callflows) { callback(null, callflows); }); }, callEntities: function(callback) { self.strategyGetCallEntities(function(callEntities) { callback(null, callEntities); }); }, voicemails: function(callback) { self.strategyGetVoicesmailBoxes(function(voicemailBoxes) { callback(null, voicemailBoxes); }); }, directories: function(callback) { self.strategyListDirectories(function(directories) { callback(null, directories); }); } }, function(err, results) { // has to be > 1 because we automatically add "undefinedMainNumber" as a placeholder for the "0" that will be automatically added if they add a Main number var hasMainNumber = (results.callflows.MainCallflow.numbers.length > 1), hasConfNumber = (results.callflows.MainConference.numbers.length > 0 && results.callflows.MainConference.numbers[0] !== 'undefinedconf'), hasFaxingNumber = (results.callflows.MainFaxing.numbers.length > 0 && results.callflows.MainFaxing.numbers[0] !== 'undefinedfaxing'), templateData = { mainNumbers: hasMainNumber ? results.callflows.MainCallflow.numbers.slice(1) : [self.i18n.active().strategy.noNumberTitle], confNumbers: hasConfNumber ? results.callflows.MainConference.numbers : [self.i18n.active().strategy.noNumberTitle], customConfGreeting: results.callflows.MainConference && ('welcome_prompt' in results.callflows.MainConference.flow.data) ? true : false, faxingNumbers: hasFaxingNumber ? results.callflows.MainFaxing.numbers : [self.i18n.active().strategy.noNumberTitle] }, template = $(self.getTemplate({ name: 'layout', data: templateData, submodule: 'strategy' })); self.strategyBindEvents(template, results); parent .empty() .append(template); monster.ui.tooltips(template); if (!hasMainNumber) { template.find('.element-container.strategy-hours,.element-container.strategy-holidays,.element-container.strategy-calls').hide(); template.find('.element-container.helper').css('display', 'block'); template.find('.element-container.main-number').css('margin-top', '10px'); self.strategyCheckFirstWalkthrough(); } else { template.find('.element-container.helper').css('display', 'none'); template.find('.element-container.main-number').css('margin-top', '0px'); self.strategyCheckSecondWalkthrough(); } if (openElement) { var element = template.find('.element-container.' + openElement + ':visible'); if (element.length > 0) { self.strategyRefreshTemplate({ container: element, strategyData: results, callback: function() { element.addClass('open'); element.find('.element-content').show(); } }); } } callback && callback(); }); self.strategyHandleFeatureCodes(); }, strategyCheckFirstWalkthrough: function() { var self = this, flagName = 'showStrategyFirstWalkthrough'; self.strategyHasWalkthrough(flagName, function() { self.strategyShowFirstWalkthrough(function() { self.strategyUpdateWalkthroughFlagUser(flagName); }); }); }, strategyCheckSecondWalkthrough: function() { var self = this, flagName = 'showStrategySecondWalkthrough'; self.strategyHasWalkthrough(flagName, function() { self.strategyShowSecondWalkthrough(function() { self.strategyUpdateWalkthroughFlagUser(flagName); }); }); }, strategyHasWalkthrough: function(name, callback) { var self = this, flag = self.uiFlags.user.get(name); if (flag !== false) { callback && callback(); } }, strategyUpdateWalkthroughFlagUser: function(flagName, callback) { var self = this, userToSave = self.uiFlags.user.set(flagName, false); self.strategyUpdateOriginalUser(userToSave, function(user) { callback && callback(user); }); }, strategyShowFirstWalkthrough: function(callback) { var self = this, mainTemplate = $('#strategy_container'), steps = [ { element: mainTemplate.find('.element-container.main-number')[0], intro: self.i18n.active().strategy.walkthrough.first.steps['1'], position: 'bottom' } ]; monster.ui.stepByStep(steps, function() { callback && callback(); }); }, strategyShowSecondWalkthrough: function(callback) { var self = this, mainTemplate = $('#strategy_container'), steps = [ { element: mainTemplate.find('.element-container.strategy-hours')[0], intro: self.i18n.active().strategy.walkthrough.second.steps['1'], position: 'bottom' }, { element: mainTemplate.find('.element-container.strategy-holidays')[0], intro: self.i18n.active().strategy.walkthrough.second.steps['2'], position: 'bottom' }, { element: mainTemplate.find('.element-container.strategy-calls')[0], intro: self.i18n.active().strategy.walkthrough.second.steps['3'], position: 'top' }, { element: mainTemplate.find('.element-container.strategy-confnum')[0], intro: self.i18n.active().strategy.walkthrough.second.steps['4'], position: 'top' }, { element: mainTemplate.find('.element-container.strategy-faxingnum')[0], intro: self.i18n.active().strategy.walkthrough.second.steps['5'], position: 'top' } ]; monster.ui.stepByStep(steps, function() { callback && callback(); }); }, /** * Show phone number choices * @param {Object} args * @param {('emergency'|'external')} args.callerIdType Caller ID type: emergency, external * @param {('choose'|'add'|'remove')} [args.action='choose'] Action being done: Choose, add or remove a number * @param {String} [args.oldNumber] Old number * @param {String[]} args.newNumbers New numbers * @param {Function} [args.save] Save callback * @param {Function} [args.cancel] Cancel callback */ strategyShowNumberChoices: function(args) { var self = this, callerIdType = args.callerIdType, oldNumber = args.oldNumber, template = $(self.getTemplate({ name: 'changeCallerIdPopup', data: { action: _.get(args, 'action', 'choose'), oldNumber: oldNumber, newNumbers: args.newNumbers, callerIdType: callerIdType }, submodule: 'strategy' })), $options = template.find('.choice'); $options.on('click', function() { $options.removeClass('active'); $(this).addClass('active'); }); template.find('.save').on('click', function() { var number = template.find('.active').data('number'); if (number === oldNumber) { _.has(args, 'cancel') && args.cancel(); } else { _.has(args, 'save') && args.save(number); } popup.dialog('close'); }); template.find('.cancel-link').on('click', function() { popup.dialog('close'); _.has(args, 'cancel') && args.cancel(); }); var popup = monster.ui.dialog(template, { title: self.i18n.active().strategy.updateCallerIdDialog.title[callerIdType] }); }, strategyBindEvents: function(template, strategyData) { var self = this, containers = template.find('.element-container'), strategyNumbersContainer = template.find('.element-container.main-number .element-content'), strategyConfNumContainer = template.find('.element-container.strategy-confnum .element-content'), strategyFaxingNumContainer = template.find('.element-container.strategy-faxingnum .element-content'), strategyHoursContainer = template.find('.element-container.strategy-hours .element-content'), strategyHolidaysContainer = template.find('.element-container.strategy-holidays .element-content'), strategyCallsContainer = template.find('.element-container.strategy-calls .element-content'); template.find('.element-header-inner').on('click', function(e) { var element = $(this).parents('.element-container'); if (element.hasClass('open')) { element.find('.element-content').slideUp(function() { element.removeClass('open'); }); } else { $.each(containers, function() { var $this = $(this); if ($this.hasClass('open')) { $this.find('.element-content').slideUp(function() { $this.removeClass('open'); }); } }); self.strategyRefreshTemplate({ container: element, strategyData: strategyData, callback: function() { element.addClass('open'); element.find('.element-content').slideDown(); } }); } }); template.find('.element-container').on('click', '.cancel-link', function(e) { e.preventDefault(); var parent = $(this).parents('.element-container'); parent.find('.element-content').slideUp(function() { parent.removeClass('open'); }); }); self.strategyNumbersBindEvents(strategyNumbersContainer, strategyData); self.strategyConfNumBindEvents(strategyConfNumContainer, strategyData); self.strategyFaxingNumBindEvents(strategyFaxingNumContainer, strategyData); self.strategyHoursBindEvents(strategyHoursContainer, strategyData); self.strategyHolidaysBindEvents(strategyHolidaysContainer, strategyData); self.strategyCallsBindEvents(strategyCallsContainer, strategyData); }, /** * Check if it is necessary to set the external caller ID for the account * @param {Object} args * @param {String[]} args.numbers Available phone numbers */ strategyCheckIfSetExternalCallerID: function(args) { var self = this, numbers = args.numbers, currAcc = monster.apps.auth.currentAccount, hasExternalCallerId = _.get(currAcc, 'caller_id.external.number', '') !== ''; if (hasExternalCallerId || _.isEmpty(numbers)) { return; } monster.waterfall([ function(callback) { if (numbers.length > 1) { self.strategyShowNumberChoices({ callerIdType: 'external', newNumbers: numbers, save: function(number) { callback(null, number); }, cancel: function() { callback(null); } }); } else { callback(null, numbers[0]); } } ], function(err, number) { if (err || !number) { return; } self.strategyChangeCallerId({ callerIdType: 'external', number: number, success: function() { monster.ui.toast({ type: 'success', message: self.getTemplate({ name: '!' + self.i18n.active().strategy.updateCallerIdDialog.success.external, data: { number: monster.util.formatPhoneNumber(number) } }) }); } }); }); }, /** * Check if it is necessary to update the emergency caller ID for the account * @param {Object} args * @param {Object[]} args.templateNumbers Template numbers * @param {String[]} args.features Number features * @param {String} args.number Number ID * @param {Object} [args.callbacks] Function callbacks * @param {Function} [args.callbacks.success] Successs callback */ strategyCheckIfUpdateEmergencyCallerID: function(args) { var self = this, templateNumbers = args.templateNumbers, features = args.features, number = args.number; // Update data we have about features for main numbers _.each(templateNumbers, function(dataLoop) { if (dataLoop.number.id === number) { dataLoop.number.features = features; } }); monster.waterfall([ function(callback) { var currAcc = monster.apps.auth.currentAccount, currentE911CallerId = _.get(currAcc, 'caller_id.emergency.number'), hasE911Feature = _.includes(features || [], 'e911'); if (hasE911Feature && _.isEmpty(currentE911CallerId)) { callback('OK', number); } else { callback(null, currentE911CallerId, hasE911Feature); } }, function(currentE911CallerId, hasE911Feature, callback) { var e911ChoicesArgs, e911Numbers = []; if (hasE911Feature) { if (currentE911CallerId !== number) { e911ChoicesArgs = { action: 'add', oldNumber: currentE911CallerId, newNumbers: [ number ] }; } } else { if (!number || currentE911CallerId === number) { e911Numbers = _.chain(templateNumbers) .filter(function(number) { return _.includes(number.number.features, 'e911'); }).map('number.id').value(); if (e911Numbers.length > 1) { e911ChoicesArgs = { action: number ? 'remove' : 'choose', newNumbers: e911Numbers }; } } } if (_.isUndefined(e911ChoicesArgs)) { callback('OK', _.head(e911Numbers)); } else { callback(null, e911ChoicesArgs); } }, function(e911ChoicesArgs, callback) { self.strategyShowNumberChoices(_.merge({ callerIdType: 'emergency', save: function(number) { callback(null, number); }, cancel: function() { callback('OK'); } }, e911ChoicesArgs)); } ], function(err, number) { if ((err && err !== 'OK') || !number) { return; } self.strategyChangeCallerId({ callerIdType: 'emergency', number: number, success: function() { monster.ui.toast({ type: 'success', message: self.getTemplate({ name: '!' + self.i18n.active().strategy.updateCallerIdDialog.success.emergency, data: { number: monster.util.formatPhoneNumber(number) } }) }); _.has(args, 'callbacks.success') && args.callbacks.success(); } }); }); }, /** * Refresh a strategy template * @param {Object} args * @param {jQuery} args.container Container template * @param {Object} args.strategyData Strategy data * @param {Function} [args.callback] Optional callback to execute after refresh */ strategyRefreshTemplate: function(args) { var self = this, $container = args.container, strategyData = args.strategyData, callback = args.callback, templateName = $container.data('template'); switch (templateName) { case 'numbers': self.strategyRefreshTemplateNumbers( _.merge( { templateName: templateName }, args) ); break; case 'confnum': self.strategyListAccountNumbers(function(accountNumbers) { var callflow = strategyData.callflows.MainConference, numbers = callflow.numbers, templateData = { hideBuyNumbers: monster.config.whitelabel.hasOwnProperty('hideBuyNumbers') ? monster.config.whitelabel.hideBuyNumbers : false, numbers: $.map(numbers, function(val, key) { if (val !== 'undefinedconf') { return { number: val }; } }), spareLinkEnabled: (_.countBy(accountNumbers, function(number) { return number.used_by ? 'assigned' : 'spare'; }).spare > 0) }, template = $(self.getTemplate({ name: 'strategy-' + templateName, data: templateData, submodule: 'strategy' })); $container .find('.element-content') .empty() .append(template); callback && callback(); }); break; case 'faxingnum': self.strategyListAccountNumbers(function(accountNumbers) { var callflow = strategyData.callflows.MainFaxing, numbers = callflow.numbers, templateData = { hideBuyNumbers: monster.config.whitelabel.hasOwnProperty('hideBuyNumbers') ? monster.config.whitelabel.hideBuyNumbers : false, numbers: $.map(numbers, function(val, key) { if (val !== 'undefinedfaxing') { return { number: val }; } }), actionLinksEnabled: _.isEmpty(callflow.flow.data), spareLinkEnabled: (_.countBy(accountNumbers, function(number) { return number.used_by ? 'assigned' : 'spare'; }).spare > 0) }, template = $(self.getTemplate({ name: 'strategy-' + templateName, data: templateData, submodule: 'strategy' })); $container .find('.element-content') .empty() .append(template); callback && callback(); }); break; case 'hours': var is12hMode = monster.apps.auth.currentUser.ui_flags && monster.apps.auth.currentUser.ui_flags.twelve_hours_mode ? true : false, secondsToTime = function(seconds) { var hours = parseInt(seconds / 3600) % 24, minutes = (parseInt(seconds / 60) % 60).toString(), suffix = ''; if (is12hMode) { suffix = hours >= 12 ? 'PM' : 'AM'; hours = hours > 12 ? hours - 12 : (hours === 0 ? 12 : hours); } return hours.toString() + ':' + (minutes.length < 2 ? '0' + minutes : minutes) + suffix; }, weekdaysRules = strategyData.temporalRules.weekdays, templateData = { alwaysOpen: true, companyTimezone: timezone.formatTimezone(strategyData.callflows.MainCallflow.flow.data.timezone || monster.apps.auth.currentAccount.timezone), days: [], lunchbreak: { enabled: (strategyData.temporalRules.lunchbreak.id in strategyData.callflows.MainCallflow.flow.children), from: secondsToTime(parseInt(strategyData.temporalRules.lunchbreak.time_window_start, 10)), to: secondsToTime(parseInt(strategyData.temporalRules.lunchbreak.time_window_stop, 10)) } }, template; _.each(self.weekdayLabels, function(val) { var isOpen = (weekdaysRules[val].id in strategyData.callflows.MainCallflow.flow.children); templateData.days.push({ name: val, label: self.i18n.active().strategy.weekdays[val.substring(4).toLowerCase()], open: isOpen, from: secondsToTime(parseInt(weekdaysRules[val].time_window_start, 10)), to: secondsToTime(parseInt(weekdaysRules[val].time_window_stop, 10)) }); if (isOpen) { templateData.alwaysOpen = false; } }); // Setting Monday to Friday enabled by default for 9AM-5PM, when switching from 24hours Open to Custom Hours. if (templateData.alwaysOpen) { _.each(templateData.days, function(val) { if (val.name !== 'MainSaturday' && val.name !== 'MainSunday') { val.open = true; val.from = is12hMode ? '9:00AM' : '9:00'; val.to = is12hMode ? '5:00PM' : '17:00'; } }); } template = $(self.getTemplate({ name: 'strategy-' + templateName, data: templateData, submodule: 'strategy' })); var validationOptions = { rules: { 'lunchbreak.from': {}, 'lunchbreak.to': { greaterDate: template.find('input[name="lunchbreak.from"]') } }, groups: { lunchbreak: 'lunchbreak.from lunchbreak.to' }, errorPlacement: function(error, element) { error.appendTo(element.parent()); } }; if (is12hMode) { validationOptions.rules['lunchbreak.from'].time12h = true; validationOptions.rules['lunchbreak.to'].time12h = true; } else { validationOptions.rules['lunchbreak.from'].time24h = true; validationOptions.rules['lunchbreak.to'].time24h = true; } _.each(self.weekdayLabels, function(wday) { validationOptions.rules['weekdays.' + wday + '.from'] = {}; validationOptions.rules['weekdays.' + wday + '.to'] = { greaterDate: template.find('input[name="weekdays.' + wday + '.from"]') }; if (is12hMode) { validationOptions.rules['weekdays.' + wday + '.from'].time12h = true; validationOptions.rules['weekdays.' + wday + '.to'].time12h = true; } else { validationOptions.rules['weekdays.' + wday + '.from'].time24h = true; validationOptions.rules['weekdays.' + wday + '.to'].time24h = true; } validationOptions.groups[wday] = 'weekdays.' + wday + '.from weekdays.' + wday + '.to'; }); monster.ui.validate(template.find('#strategy_custom_hours_form'), validationOptions); $container .find('.element-content') .empty() .append(template); monster.ui.timepicker(template.find('.timepicker')); callback && callback(); break; case 'holidays': var templateData = { enabled: !$.isEmptyObject(strategyData.temporalRules.holidays) }, template = $(self.getTemplate({ name: 'strategy-' + templateName, data: templateData, submodule: 'strategy' })), holidayList = template.find('.holidays-list'); $container .find('.element-content') .empty() .append(template); holidayList.empty(); _.each(strategyData.temporalRules.holidays, function(val, key) { if (val.id in strategyData.callflows.MainCallflow.flow.children) { var holidayType, holidayData = { id: val.id, name: val.name, fromMonth: val.month }; if (val.hasOwnProperty('ordinal')) { holidayType = 'advanced'; holidayData.ordinal = val.ordinal; holidayData.wday = val.wdays[0]; } else { if (val.hasOwnProperty('viewData')) { holidayType = 'range'; holidayData.fromDay = val.viewData.fromDay; holidayData.fromMonth = val.viewData.fromMonth; holidayData.toDay = val.viewData.toDay; holidayData.toMonth = val.viewData.toMonth; holidayData.set = true; } else { holidayData.fromDay = val.days[0]; if (val.days.length > 1) { holidayType = 'range'; holidayData.toDay = val.days[val.days.length - 1]; holidayData.toMonth = val.month; } else { holidayType = 'single'; } } } self.strategyRenderHolidayLine(holidayList, holidayType, holidayData); } }); callback && callback(); break; case 'calls': var templateData = { lunchbreak: (strategyData.temporalRules.lunchbreak.id in strategyData.callflows.MainCallflow.flow.children), holidays: !$.isEmptyObject(strategyData.temporalRules.holidays), afterhours: false }, template; _.each(self.weekdayLabels, function(val) { if (strategyData.temporalRules.weekdays[val].id in strategyData.callflows.MainCallflow.flow.children) { templateData.afterhours = true; } }); template = $(self.getTemplate({ name: 'strategy-' + templateName, data: templateData, submodule: 'strategy' })); $container .find('.element-content') .empty() .append(template); $.each(template.find('.callflow-tab'), function() { var $this = $(this), callflowName = $this.data('callflow'), menuName = callflowName + 'Menu', tabData = { callOption: { type: 'default' }, hideAdvancedCallflows: _.isEmpty(strategyData.callEntities.advancedCallflows), callflow: callflowName, callEntities: self.strategyGetCallEntitiesDropdownData(strategyData.callEntities, true, true), voicemails: strategyData.voicemails, tabMessage: self.i18n.active().strategy.calls.callTabsMessages[callflowName] }; if (strategyData.callflows[callflowName].flow.hasOwnProperty('is_main_number_cf')) { tabData.callOption.callEntityId = strategyData.callflows[callflowName].flow.data.id; tabData.callOption.type = 'advanced-callflow'; } else if (strategyData.callflows[callflowName].flow.module === 'voicemail') { tabData.callOption.callEntityId = 'none'; tabData.callOption.voicemailId = strategyData.callflows[callflowName].flow.data.id; tabData.callOption.type = 'user-voicemail'; } else if (!_.isEmpty(strategyData.callflows[callflowName].flow.children)) { tabData.callOption.callEntityId = strategyData.callflows[callflowName].flow.data.id; if ('_' in strategyData.callflows[callflowName].flow.children && strategyData.callflows[callflowName].flow.children._.module === 'voicemail') { tabData.callOption.type = 'user-voicemail'; tabData.callOption.voicemailId = strategyData.callflows[callflowName].flow.children._.data.id; } else { tabData.callOption.type = 'user-menu'; } } if (menuName in strategyData.callflows) { tabData.menu = menuName; } $(this) .empty() .append($(self.getTemplate({ name: 'callsTab', data: tabData, submodule: 'strategy' }))); }); $.each(template.find('.user-select select'), function() { var $this = $(this); monster.ui.chosen($this, { width: '160px' }); $this.siblings('.title').text($this.find('option:selected').closest('optgroup').prop('label')); }); monster.ui.chosen(template.find('.voicemail-select select'), { width: '160px' }); monster.ui.chosen(template.find('.advancedCallflows-select select'), { width: '160px' }); callback && callback(); break; default: callback && callback(); break; } }, /** * Refresh main numbers strategy template * @param {Object} args * @param {jQuery} args.container Container template * @param {Object} args.strategyData Strategy data * @param {Object} args.templateName Template name * @param {Function} [args.callback] Optional callback to execute after refresh */ strategyRefreshTemplateNumbers: function(args) { var self = this, $container = args.container, strategyData = args.strategyData, templateName = args.templateName, callback = args.callback; self.strategyListAccountNumbers(function(accountNumbers) { var callflow = strategyData.callflows.MainCallflow, templateData = { hideBuyNumbers: _.has(monster.config.whitelabel, 'hideBuyNumbers') ? monster.config.whitelabel.hideBuyNumbers : false, numbers: _.map(self.strategyExtractMainNumbers({ mainCallflow: callflow }), function(val) { var ret = { number: { id: val } }; if (_.has(accountNumbers, val)) { ret.number = _.merge(accountNumbers[val], ret.number); } return ret; }), spareLinkEnabled: (_.countBy(accountNumbers, function(number) { return number.used_by ? 'assigned' : 'spare'; }).spare > 0) }, template = $(self.getTemplate({ name: 'strategy-' + templateName, data: templateData, submodule: 'strategy' })), afterFeatureUpdate = function afterFeatureUpdate(numberId, numberFeatures, callbacks) { if (numberId) { monster.ui.paintNumberFeaturesIcon(numberFeatures, template.find('[data-phonenumber="' + numberId + '"] .features')); } self.strategyCheckIfUpdateEmergencyCallerID({ templateNumbers: templateData.numbers, features: numberFeatures, number: numberId, callbacks: callbacks }); }; _.each(templateData.numbers, function(data) { data.number.phoneNumber = data.number.id; var numberData = data.number, args = { target: template.find('[data-phonenumber="' + numberData.id + '"] .edit-features'), numberData: numberData, afterUpdate: function(features) { afterFeatureUpdate(numberData.id, features); } }; monster.pub('common.numberFeaturesMenu.render', args); }); monster.ui.tooltips(template); $container .find('.element-content') .empty() .append(template); callback && callback(); }); }, /** * Changes a caller ID for the current account * @param {Object} args * @param {('emergency'|'external')} args.callerIdType Caller ID type: emergency, external * @param {String} args.number Number to be set for emergency calls * @param {Function} [args.success] Success callback * @param {Function} [args.error] Error callback */ strategyChangeCallerId: function(args) { var self = this, callerIdType = args.callerIdType, number = args.number; monster.waterfall([ function(callback) { self.strategyGetAccount({ success: function(data) { callback(null, data); }, error: function(parsedError) { callback(parsedError); } }); }, function(data, callback) { data.caller_id = data.caller_id || {}; data.caller_id[callerIdType] = data.caller_id[callerIdType] || {}; data.caller_id[callerIdType].number = number; self.strategyUpdateAccount({ data: data, success: function() { callback(null); }, error: function(parsedError) { callback(parsedError); } }); } ], function(err) { if (err) { _.has(args, 'error') && args.error(err); } else { _.has(args, 'success') && args.success(number); } }); }, strategyNumbersBindEvents: function(container, strategyData) { var self = this, addNumbersToMainCallflow = function(numbers) { if (_.isEmpty(numbers)) { return; } var mainCallflow = strategyData.callflows.MainCallflow, indexPlaceholder = mainCallflow.numbers.indexOf('undefinedMainNumber'); if (indexPlaceholder >= 0) { mainCallflow.numbers[indexPlaceholder] = '0'; } mainCallflow.numbers = mainCallflow.numbers.concat(numbers); self.strategyUpdateCallflow(mainCallflow, function(updatedCallflow) { strategyData.callflows.MainCallflow = updatedCallflow; refreshNumbersTemplate(); self.strategyCheckIfSetExternalCallerID({ numbers: self.strategyExtractMainNumbers({ mainCallflow: updatedCallflow }) }); }); }, refreshNumbersTemplate = function() { var mainCallflow = strategyData.callflows.MainCallflow, parentContainer = container.parents('.element-container'), headerSpan = parentContainer.find('.element-header-inner .summary > span'); // Refresh headers if (mainCallflow.numbers.length > 1) { headerSpan.html(monster.util.formatPhoneNumber(mainCallflow.numbers[1])); if (mainCallflow.numbers.length > 3) { headerSpan.append(''); } else if (mainCallflow.numbers.length === 3) { headerSpan.append(', ' + monster.util.formatPhoneNumber(mainCallflow.numbers[2])); } container.parents('#strategy_container').find('.element-container:not(.main-number,.strategy-confnum)').show(); container.parents('#strategy_container').find('.element-container:not(.main-number,.strategy-faxingnum)').show(); container.parents('#strategy_container').find('.element-container.helper').hide(); container.parents('#strategy_container').find('.element-container.main-number').css('margin-top', '0px'); self.strategyCheckSecondWalkthrough(); } else { headerSpan.html(self.i18n.active().strategy.noNumberTitle); container.parents('#strategy_container').find('.element-container:not(.main-number,.strategy-confnum)').hide(); container.parents('#strategy_container').find('.element-container:not(.main-number,.strategy-faxingnum)').hide(); container.parents('#strategy_container').find('.element-container.helper').show(); container.parents('#strategy_container').find('.element-container.main-number').css('margin-top', '10px'); } // Refresh template self.strategyRefreshTemplate({ container: parentContainer, strategyData: strategyData }); }; container.on('click', '.action-links .spare-link:not(.disabled)', function(e) { e.preventDefault(); var args = { accountName: monster.apps.auth.currentAccount.name, accountId: self.accountId, callback: function(numberList) { var numbers = _.map(numberList, function(val) { return val.phoneNumber; }); addNumbersToMainCallflow(numbers); } }; monster.pub('common.numbers.dialogSpare', args); }); container.on('click', '.action-links .buy-link', function(e) { e.preventDefault(); monster.pub('common.buyNumbers', { searchType: $(this).data('type'), callbacks: { success: function(numbers) { addNumbersToMainCallflow(_.keys(numbers)); monster.ui.toast({ type: 'success', message: self.i18n.active().strategy.toastrMessages.buyNumbersSuccess }); }, error: function(error) { monster.ui.toast({ type: 'error', message: self.i18n.active().strategy.toastrMessages.buyNumbersError }); } } }); }); container.on('click', '.action-links .port-link', function(e) { e.preventDefault(); }); container.on('click', '.number-element .remove-number', function(e) { e.preventDefault(); var $this = $(this), numberToRemove = $this.data('number'), e911Feature = $this.data('e911'), isE911Enabled = monster.util.isNumberFeatureEnabled('e911'), indexToRemove = strategyData.callflows.MainCallflow.numbers.indexOf(numberToRemove.toString()); if (e911Feature === 'active' && container.find('.number-element .features .feature-e911').length === 1 && isE911Enabled) { monster.ui.alert('error', self.i18n.active().strategy.alertMessages.lastE911Error); } else if (indexToRemove >= 0) { self.strategyGetNumber(numberToRemove, function(dataNumber) { var dataTemplate = { phoneNumber: numberToRemove }, featureList = [], popupHtml, popup, updateCallflow = function() { monster.waterfall([ function(callback) { strategyData.callflows.MainCallflow.numbers.splice(indexToRemove, 1); // We don't want the '0' to stay in the routing system if they're no longer using SmartPBX. // If they remove their last main number, we consider they don't use SmartPBX, so we reset the "0" to be the "undefinedMainNumber" if (strategyData.callflows.MainCallflow.numbers.length === 1 && strategyData.callflows.MainCallflow.numbers[0] === '0') { strategyData.callflows.MainCallflow.numbers[0] = 'undefinedMainNumber'; } self.strategyUpdateCallflow(strategyData.callflows.MainCallflow, function(updatedCallflow) { callback(null, updatedCallflow); }); }, function(updatedCallflow, callback) { monster.ui.toast({ type: 'success', message: self.i18n.active().strategy.toastrMessages.removeNumberSuccess }); strategyData.callflows.MainCallflow = updatedCallflow; refreshNumbersTemplate(); self.strategyGetAccount({ success: function(accountData) { callback(null, accountData); } }); } ], function(err, accountData) { if (err) { return; } var modified = false; // TODO: Maybe before unsetting external caller_id, ask user to choose another, if any other number is available if (_.get(accountData, 'caller_id.external.number') === numberToRemove) { delete accountData.caller_id.external; modified = true; } if (_.get(accountData, 'caller_id.emergency.number') === numberToRemove) { delete accountData.caller_id.emergency; modified = true; } if (modified) { self.strategyUpdateAccount({ data: accountData }); } }); }; _.each(dataNumber, function(val, idx) { if (idx === 'cnam' || idx === 'e911') { featureList.push({ name: idx, friendlyName: self.i18n.active().strategy.popupRemoveFeatures.features[idx] }); } }); if (featureList.length > 0) { dataTemplate.featureList = featureList; popupHtml = $(self.getTemplate({ name: 'popupRemoveFeatures', data: dataTemplate, submodule: 'strategy' })); popup = monster.ui.dialog(popupHtml, { title: self.i18n.active().strategy.popupRemoveFeatures.title, width: '540px' }); popup.find('.cancel-link').on('click', function() { popup.dialog('close'); }); popup.find('#remove_features').on('click', function() { popup.find('.table td').each(function(idx, elem) { if ($(elem).find('input').is(':checked')) { delete dataNumber[$(elem).data('name')]; } }); self.strategyUpdateNumber(numberToRemove, dataNumber, function() { popup.dialog('close'); updateCallflow(); }); }); } else { updateCallflow(); } }); } }); }, strategyConfNumBindEvents: function(container, strategyData) { var self = this, addNumbersToMainConference = function(numbers) { if (numbers.length) { var mainConference = strategyData.callflows.MainConference; if (mainConference.numbers.length <= 1 && mainConference.numbers[0] === 'undefinedconf') { mainConference.numbers = []; } mainConference.numbers = mainConference.numbers.concat(numbers); self.strategyUpdateCallflow(mainConference, function(updatedCallflow) { var parentContainer = container.parents('.element-container'); strategyData.callflows.MainConference = updatedCallflow; refreshConfNumHeader(parentContainer); self.strategyRefreshTemplate({ container: parentContainer, strategyData: strategyData }); }); } }, refreshConfNumHeader = function(parentContainer) { var mainConference = strategyData.callflows.MainConference, headerSpan = parentContainer.find('.element-header-inner .summary > span'); if (mainConference.numbers.length > 0 && mainConference.numbers[0] !== 'undefinedconf') { headerSpan.html(monster.util.formatPhoneNumber(mainConference.numbers[0])); if (mainConference.numbers.length > 2) { headerSpan.append(''); } else if (mainConference.numbers.length === 2) { headerSpan.append(', ' + monster.util.formatPhoneNumber(mainConference.numbers[1])); } } else { headerSpan.html(self.i18n.active().strategy.noNumberTitle); } }; container.on('click', '.action-links .spare-link:not(.disabled)', function(e) { e.preventDefault(); var args = { accountName: monster.apps.auth.currentAccount.name, accountId: self.accountId, callback: function(numberList) { var numbers = $.map(numberList, function(val) { return val.phoneNumber; }); addNumbersToMainConference(numbers); } }; monster.pub('common.numbers.dialogSpare', args); }); container.on('click', '.action-links .greeting-link', function(e) { e.preventDefault(); var confCallflow = strategyData.callflows.MainConference; if (confCallflow) { self.strategyGetMainConferenceGreetingMedia(confCallflow, function(greetingMedia) { var greetingTemplate = $(self.getTemplate({ name: 'customConferenceGreeting', data: { enabled: ('welcome_prompt' in confCallflow.flow.data), greeting: greetingMedia && greetingMedia.tts ? greetingMedia.tts.text : '' }, submodule: 'strategy' })), greetingPopup = monster.ui.dialog(greetingTemplate, { title: self.i18n.active().strategy.customConferenceGreeting.title, position: ['center', 20] }); greetingTemplate.find('.switch-state').on('change', function() { $(this).prop('checked') ? greetingTemplate.find('.content').slideDown() : greetingTemplate.find('.content').slideUp(); }); greetingTemplate.find('.cancel').on('click', function() { greetingPopup.dialog('close').remove(); }); greetingTemplate.find('.save').on('click', function() { if (greetingTemplate.find('.switch-state').prop('checked')) { var updateMedia = function(callback) { if (greetingMedia) { greetingMedia.description = ''; greetingMedia.media_source = 'tts'; greetingMedia.tts = { text: greetingTemplate.find('.custom-greeting-text').val(), voice: 'female/en-US' }; self.callApi({ resource: 'media.update', data: { accountId: self.accountId, mediaId: greetingMedia.id, data: greetingMedia }, success: function(data, status) { callback && callback(data.data); } }); } else { self.callApi({ resource: 'media.create', data: { accountId: self.accountId, data: { description: '', media_source: 'tts', name: 'MainConferenceGreeting', streamable: true, type: 'mainConfGreeting', tts: { text: greetingTemplate.find('.custom-greeting-text').val(), voice: 'female/en-US' } } }, success: function(data, status) { callback && callback(data.data); } }); } }; updateMedia(function(updatedGreeting) { confCallflow.flow.data.welcome_prompt = { media_id: updatedGreeting.id }; self.strategyUpdateCallflow(confCallflow, function(updatedCallflow) { strategyData.callflows.MainConference = updatedCallflow; greetingPopup.dialog('close').remove(); $('#strategy_container .custom-greeting-icon').show(); }); }); } else { if ('welcome_prompt' in confCallflow.flow.data) { delete confCallflow.flow.data.welcome_prompt; self.strategyUpdateCallflow(confCallflow, function(updatedCallflow) { strategyData.callflows.MainConference = updatedCallflow; greetingPopup.dialog('close').remove(); $('#strategy_container .custom-greeting-icon').hide(); }); } else { greetingPopup.dialog('close').remove(); } } }); }); } else { monster.ui.alert('error', self.i18n.active().strategy.customConferenceGreeting.mainConfMissing); } }); container.on('click', '.action-links .buy-link', function(e) { e.preventDefault(); monster.pub('common.buyNumbers', { searchType: $(this).data('type'), callbacks: { success: function(numbers) { addNumbersToMainConference(_.keys(numbers)); monster.ui.toast({ type: 'success', message: self.i18n.active().strategy.toastrMessages.buyNumbersSuccess }); }, error: function(error) { monster.ui.toast({ type: 'error', message: self.i18n.active().strategy.toastrMessages.buyNumbersError }); } } }); }); container.on('click', '.number-element .remove-number', function(e) { e.preventDefault(); var numberToRemove = $(this).data('number').toString(), indexToRemove = strategyData.callflows.MainConference.numbers.indexOf(numberToRemove); if (indexToRemove >= 0) { strategyData.callflows.MainConference.numbers.splice(indexToRemove, 1); if (strategyData.callflows.MainConference.numbers.length === 0) { strategyData.callflows.MainConference.numbers = ['undefinedconf']; } self.strategyUpdateCallflow(strategyData.callflows.MainConference, function(updatedCallflow) { var parentContainer = container.parents('.element-container'); monster.ui.toast({ type: 'success', message: self.i18n.active().strategy.toastrMessages.removeNumberSuccess }); strategyData.callflows.MainConference = updatedCallflow; refreshConfNumHeader(parentContainer); self.strategyRefreshTemplate({ container: parentContainer, strategyData: strategyData }); }); } }); }, strategyGetMainConferenceGreetingMedia: function(mainConferenceCallflow, callback) { var self = this, mediaId = _.get(mainConferenceCallflow, 'flow.data.welcome_prompt.media_id'); monster.waterfall([ function(cb) { if (_.isUndefined(mediaId)) { return cb(null, null); } self.callApi({ resource: 'media.get', data: { accountId: self.accountId, mediaId: mediaId }, success: function(data) { cb(null, data.data); }, error: function() { cb(true); } }); } ], function(err, media) { callback(err ? null : media); }); }, strategyFaxingNumBindEvents: function(container, strategyData) { var self = this, addNumbersToMainFaxing = function(numbers) { if (numbers.length) { var mainFaxing = strategyData.callflows.MainFaxing, updateCallflow = function() { self.strategyUpdateCallflow(mainFaxing, function(updatedCallflow) { var parentContainer = container.parents('.element-container'); strategyData.callflows.MainFaxing = updatedCallflow; refreshFaxingNumHeader(parentContainer); self.strategyRefreshTemplate({ container: parentContainer, strategyData: strategyData }); }); }; if (mainFaxing.numbers.length <= 1 && mainFaxing.numbers[0] === 'undefinedfaxing') { mainFaxing.numbers = []; } mainFaxing.numbers = mainFaxing.numbers.concat(numbers); if (mainFaxing.flow.data.hasOwnProperty('id')) { updateCallflow(); } else { var template = $(self.getTemplate({ name: 'popupEditFaxbox', submodule: 'strategy' })), popup = monster.ui.dialog(template, { title: self.i18n.active().strategy.popupEditFaxbox.titles.create, position: ['center', 20], hideClose: true }); template .find('.cancel-link') .on('click', function(event) { event.preventDefault(); popup.dialog('close').remove(); }); template .find('.save') .on('click', function(event) { event.preventDefault(); var $form = template.find('#faxbox_form'), email = monster.ui.getFormData('faxbox_form').email; monster.ui.validate($form, { rules: { email: { required: true, email: true } } }); if (monster.ui.valid($form)) { popup.dialog('close').remove(); self.strategyBuildFaxbox({ data: { email: email, number: mainFaxing.numbers[0] }, success: function(data) { mainFaxing.flow.data.id = data.id; updateCallflow(); } }); } }); } } }, refreshFaxingNumHeader = function(parentContainer) { var mainFaxing = strategyData.callflows.MainFaxing, headerSpan = parentContainer.find('.element-header-inner .summary > span'); if (mainFaxing.numbers.length > 0 && mainFaxing.numbers[0] !== 'undefinedfaxing') { headerSpan.html(monster.util.formatPhoneNumber(mainFaxing.numbers[0])); if (mainFaxing.numbers.length > 2) { headerSpan.append(''); } else if (mainFaxing.numbers.length === 2) { headerSpan.append(', ' + monster.util.formatPhoneNumber(mainFaxing.numbers[1])); } } else { headerSpan.html(self.i18n.active().strategy.noNumberTitle); } }; container.on('click', '.action-links .spare-link:not(.disabled)', function(e) { e.preventDefault(); var args = { accountName: monster.apps.auth.currentAccount.name, accountId: self.accountId, singleSelect: true, callback: function(numberList) { var numbers = $.map(numberList, function(val) { return val.phoneNumber; }); addNumbersToMainFaxing(numbers); } }; monster.pub('common.numbers.dialogSpare', args); }); container.on('click', '.action-links .edit-email', function(e) { e.preventDefault(); monster.waterfall([ function(callback) { self.strategyGetFaxbox({ data: { faxboxId: strategyData.callflows.MainFaxing.flow.data.id }, success: function(faxbox) { var template = $(self.getTemplate({ name: 'popupEditFaxbox', data: { email: faxbox.hasOwnProperty('notifications') && faxbox.notifications.hasOwnProperty('inbound') && faxbox.notifications.inbound.hasOwnProperty('email') ? faxbox.notifications.inbound.email.send_to : '' }, submodule: 'strategy' })), popup = monster.ui.dialog(template, { title: self.i18n.active().strategy.popupEditFaxbox.titles.edit, position: ['center', 20] }); template .find('.cancel-link') .on('click', function(event) { event.preventDefault(); popup.dialog('close').remove(); callback(true, null); }); template .find('.save') .on('click', function(event) { event.preventDefault(); var $form = template.find('#faxbox_form'), email = monster.ui.getFormData('faxbox_form').email; monster.ui.validate($form, { rules: { email: { required: true, email: true } } }); if (monster.ui.valid($form)) { popup.dialog('close').remove(); callback(null, _.extend(faxbox, { notifications: { inbound: { email: { send_to: email } } } })); } }); } }); }, function(faxboxData, callback) { self.strategyUpdateFaxbox({ data: { faxboxId: faxboxData.id, data: faxboxData }, success: function(updatedFaxbox) { callback(null, updatedFaxbox); } }); } ], function(err, results) { if (!err) { monster.ui.toast({ type: 'success', message: 'Main Fabox Email Successfully Changed' }); } }); }); container.on('click', '.action-links .buy-link', function(e) { e.preventDefault(); monster.pub('common.buyNumbers', { searchType: $(this).data('type'), singleSelect: true, callbacks: { success: function(numbers) { addNumbersToMainFaxing(_.keys(numbers)); monster.ui.toast({ type: 'success', message: self.i18n.active().strategy.toastrMessages.buyNumbersSuccess }); }, error: function(error) { monster.ui.toast({ type: 'error', message: self.i18n.active().strategy.toastrMessages.buyNumbersError }); } } }); }); container.on('click', '.number-element .remove-number', function(e) { e.preventDefault(); var numberToRemove = $(this).data('number'), mainFaxing = strategyData.callflows.MainFaxing, indexToRemove = mainFaxing.numbers.indexOf(numberToRemove); if (indexToRemove >= 0) { mainFaxing.numbers.splice(indexToRemove, 1); if (mainFaxing.numbers.length === 0) { mainFaxing.numbers = ['undefinedfaxing']; } self.strategyDeleteFaxbox({ data: { id: mainFaxing.flow.data.id }, success: function(data) { delete mainFaxing.flow.data.id; self.strategyUpdateCallflow(mainFaxing, function(updatedCallflow) { var parentContainer = container.parents('.element-container'); monster.ui.toast({ type: 'success', message: self.i18n.active().strategy.toastrMessages.removeNumberSuccess }); strategyData.callflows.MainFaxing = updatedCallflow; refreshFaxingNumHeader(parentContainer); self.strategyRefreshTemplate({ container: parentContainer, strategyData: strategyData }); }); } }); } }); }, strategyHoursBindEvents: function(container, strategyData) { var self = this; container.on('change', '.custom-hours-toggler input[type="radio"]', function(e) { var toggleDiv = container.find('.custom-hours-div'); if ($(this).val() === 'true') { toggleDiv.slideDown(); } else { toggleDiv.slideUp(); } }); container.on('change', '.custom-days input[type="checkbox"]', function(e) { var parent = $(this).parents('.custom-day'), timepickers = parent.find('.timepickers'), status = parent.find('.status'); if ($(this).prop('checked')) { timepickers.fadeIn(200); status.fadeOut(100, function() { status.html(self.i18n.active().strategy.open); status.fadeIn(100); }); } else { timepickers.fadeOut(200); status.fadeOut(100, function() { status.html(self.i18n.active().strategy.closed); status.fadeIn(100); }); } }); container.on('change', '.custom-hours-lunchbreak input[type="checkbox"]', function(e) { if ($(this).prop('checked')) { $(this).parents('.custom-hours-lunchbreak').find('.timepickers').fadeIn(200); } else { $(this).parents('.custom-hours-lunchbreak').find('.timepickers').fadeOut(200); } }); container.on('click', '.save-button', function(e) { e.preventDefault(); if (monster.ui.valid(container.find('#strategy_custom_hours_form'))) { var parent = $(this).parents('.element-container'), customHours = monster.ui.getFormData('strategy_custom_hours_form'), mainCallflow = strategyData.callflows.MainCallflow, formatChildModule = function(callflowId) { return { children: {}, data: { id: callflowId }, module: 'callflow' }; }; _.each(strategyData.temporalRules.weekdays, function(val, key) { delete mainCallflow.flow.children[val.id]; }); delete mainCallflow.flow.children[strategyData.temporalRules.lunchbreak.id]; if (customHours.enabled === 'false' || !customHours.opendays || customHours.opendays.length === 0) { mainCallflow.flow.children._ = formatChildModule(strategyData.callflows.MainOpenHours.id); } else { var tmpRulesRequests = {}; mainCallflow.flow.children._ = formatChildModule(strategyData.callflows.MainAfterHours.id); if (customHours.lunchbreak.enabled) { var lunchbreakRule = strategyData.temporalRules.lunchbreak; _.assign(lunchbreakRule, { wdays: _.map(customHours.opendays, function(item) { // make sure the lunch rule is only valid on days the business is open return _ .chain(item) .toLower() .replace(/^main/, '') .value(); }), time_window_start: monster.util.timeToSeconds(customHours.lunchbreak.from), time_window_stop: monster.util.timeToSeconds(customHours.lunchbreak.to) }); tmpRulesRequests.lunchbreak = function(callback) { self.callApi({ resource: 'temporalRule.update', data: { accountId: self.accountId, ruleId: lunchbreakRule.id, data: lunchbreakRule }, success: function(data, status) { callback(null, data.data); } }); }; mainCallflow.flow.children[lunchbreakRule.id] = formatChildModule(strategyData.callflows.MainLunchHours.id); } _.each(customHours.opendays, function(val) { var temporalRule = strategyData.temporalRules.weekdays[val]; temporalRule.time_window_start = monster.util.timeToSeconds(customHours.weekdays[val].from); temporalRule.time_window_stop = monster.util.timeToSeconds(customHours.weekdays[val].to); tmpRulesRequests[val] = function(callback) { self.callApi({ resource: 'temporalRule.update', data: { accountId: self.accountId, ruleId: temporalRule.id, data: temporalRule }, success: function(data, status) { callback(null, data.data); } }); }; mainCallflow.flow.children[temporalRule.id] = formatChildModule(strategyData.callflows.MainOpenHours.id); }); monster.parallel(tmpRulesRequests, function(err, results) {}); } self.strategyRebuildMainCallflowRuleArray(strategyData); self.strategyUpdateCallflow(mainCallflow, function(updatedCallflow) { strategyData.callflows.MainCallflow = updatedCallflow; parent.find('.element-content').hide(); parent.removeClass('open'); }); } }); }, strategyHolidaysBindEvents: function(container, strategyData) { var self = this; container.on('change', '.holidays-toggler input[type="checkbox"]', function(e) { if ($(this).prop('checked')) { container.find('.holidays-div').slideDown(); } else { container.find('.holidays-div').slideUp(); } }); container.on('click', '.add-holidays-link', function(e) { e.preventDefault(); self.strategyRenderHolidayLine(container.find('.holidays-list'), $(this).data('type')); }); container.on('click', '.delete-holiday', function(e) { var holidaysElement = $(this).parents('.holidays-element'), id = holidaysElement.data('id'), type = holidaysElement.data('type'); if (id) { monster.ui.confirm(self.i18n.active().strategy.confirmMessages.deleteHoliday, function() { var mainCallflow = strategyData.callflows.MainCallflow; delete mainCallflow.flow.children[id]; self.strategyRebuildMainCallflowRuleArray(strategyData); self.strategyUpdateCallflow(mainCallflow, function(updatedCallflow) { strategyData.callflows.MainCallflow = updatedCallflow; var afterDelete = function(data) { delete strategyData.temporalRules.holidays[data.name]; holidaysElement.remove(); }; if (type === 'set') { self.strategyDeleteRuleSetAndRules(id, afterDelete); } else { self.strategyDeleteHoliday(id, afterDelete); } }); }); } else { holidaysElement.remove(); } }); container.on('click', '.save-button', function(e) { e.preventDefault(); var parent = $(this).parents('.element-container'), mainCallflow = strategyData.callflows.MainCallflow, holidaysEnabled = parent.find('.holidays-toggler input[type="checkbox"]')[0].checked, holidayRulesRequests = {}, invalidData = false; if (holidaysEnabled) { $.each(container.find('.holidays-element'), function() { var holidayRule = self.strategyBuildHolidayRule($(this), holidayRulesRequests); if (!holidayRule) { invalidData = true; return false; } holidayRulesRequests[holidayRule.name] = function(callback) { // ghetto strategyBuildHoliday builds a complete different object for a range, so we check if one of the different key is in there, if yes, this is a range spanning multiple months if (holidayRule.hasOwnProperty('isRange')) { self.strategyBuildMultiMonthRangeHoliday(holidayRule, function(data) { data.viewData = holidayRule; callback && callback(null, data); }); } else { self.strategyCleanUpdateHoliday(holidayRule, function(data) { callback && callback(null, data); }); } }; }); if (invalidData) { monster.ui.alert(self.i18n.active().strategy.alertMessages.uniqueHoliday); } else { monster.parallel(holidayRulesRequests, function(err, results) { // First extract all ids from the new holidayList var existingHolidaysCallflowsIds = [], newHolidayCallflowsIds = _.map(holidayRulesRequests, 'id'); // Find all IDs of existing Callflows in the Main Callflow that are linking to the Main Holidays _.each(mainCallflow.flow.children, function(directChild, id) { if (id !== '_' && directChild.data.id === strategyData.callflows.MainHolidays.id) { existingHolidaysCallflowsIds.push(id); } }); // Now see if any of these existing IDs that are no longer in the list of holidays // If we find orphans, remove them from the main callflow _.each(existingHolidaysCallflowsIds, function(id) { if (newHolidayCallflowsIds.indexOf(id) < 0) { delete mainCallflow.flow.children[id]; } }); _.each(results, function(val, key) { mainCallflow.flow.children[val.id] = { children: {}, data: { id: strategyData.callflows.MainHolidays.id }, module: 'callflow' }; strategyData.temporalRules.holidays[val.name] = val; }); self.strategyRebuildMainCallflowRuleArray(strategyData); self.strategyUpdateCallflow(mainCallflow, function(updatedCallflow) { strategyData.callflows.MainCallflow = updatedCallflow; parent.find('.element-content').hide(); parent.removeClass('open'); monster.ui.toast({ type: 'success', message: self.i18n.active().strategy.toastrMessages.updateHolidaySuccess }); }); }); } } else { monster.ui.confirm(self.i18n.active().strategy.confirmMessages.disableHolidays, function() { _.each(strategyData.temporalRules.holidays, function(val, key) { holidayRulesRequests[key] = function(callback) { if (val.hasOwnProperty('temporal_rules')) { self.strategyDeleteRuleSetAndRules(val.id, function() { delete mainCallflow.flow.children[val.id]; callback(null, {}); }); } else { self.strategyDeleteHoliday(val.id, function() { delete mainCallflow.flow.children[val.id]; callback(null, {}); }); } }; }); monster.parallel(holidayRulesRequests, function(err, results) { strategyData.temporalRules.holidays = {}; self.strategyRebuildMainCallflowRuleArray(strategyData); self.strategyUpdateCallflow(mainCallflow, function(updatedCallflow) { strategyData.callflows.MainCallflow = updatedCallflow; parent.find('.element-content').hide(); parent.removeClass('open'); monster.ui.toast({ type: 'success', message: self.i18n.active().strategy.toastrMessages.updateHolidaySuccess }); }); }); }); } }); }, strategyCleanUpdateHoliday: function(data, callback) { var self = this, updateHoliday = function() { delete data.extra; self.strategyUpdateHoliday(data, function(data) { callback && callback(data); }); }; if (data.extra.oldType === 'set') { self.strategyDeleteRuleSetAndRules(data.id, function() { delete data.id; updateHoliday(); }); } else { updateHoliday(); } }, strategyBuildMultiMonthRangeHoliday: function(data, globalCallback) { var self = this, fromDay = parseInt(data.fromDay), fromMonth = parseInt(data.fromMonth), toDay = parseInt(data.toDay), toMonth = parseInt(data.toMonth), name = data.name, getMonthRule = function(name, pMonth, pStartDay, pEndDay) { var month = parseInt(pMonth), fromDay = pStartDay || 1, toDay = pEndDay || 31, days = []; for (var day = fromDay; day <= toDay; day++) { days.push(day); } return { name: name + '_' + month, cycle: 'yearly', days: days, interval: 1, month: month }; }, rulesToCreate = [ ], ruleSet = { name: name, temporal_rules: [], type: 'main_holidays' }, parallelRequests = {}, junkName = name + '_' + monster.util.randomString(6); if (fromMonth !== toMonth) { rulesToCreate.push(getMonthRule(junkName, fromMonth, fromDay, 31)); var firstMonthLoop = fromMonth === 12 ? 1 : fromMonth + 1; for (var loopMonth = firstMonthLoop; (loopMonth !== toMonth && (loopMonth - 12) !== toMonth); loopMonth++) { if (loopMonth === 13) { loopMonth = 1; } rulesToCreate.push(getMonthRule(junkName, loopMonth, 1, 31)); } rulesToCreate.push(getMonthRule(junkName, toMonth, 1, toDay)); } else { rulesToCreate.push(getMonthRule(junkName, fromMonth, fromDay, toDay)); } _.each(rulesToCreate, function(rule) { parallelRequests[rule.name] = function(callback) { self.strategyUpdateHoliday(rule, function(data) { callback && callback(null, data); }); }; }); var createCleanSet = function() { // Create All Rules, and then Create Rule Set. monster.parallel(parallelRequests, function(err, results) { _.each(rulesToCreate, function(rule) { ruleSet.temporal_rules.push(results[rule.name].id); }); self.strategyCreateRuleSet(ruleSet, function(data) { globalCallback(data); }); }); }; if (data.hasOwnProperty('id')) { if (data.extra.oldType === 'rule') { self.strategyDeleteHoliday(data.id, function() { createCleanSet(); }); } else { self.strategyDeleteRuleSetAndRules(data.id, function() { createCleanSet(); }); } } else { createCleanSet(); } }, strategyGetDetailRuleSet: function(id, globalCallback) { var self = this; self.strategyGetRuleSet(id, function(set) { var parallelRequests = {}; _.each(set.temporal_rules, function(ruleId) { parallelRequests[ruleId] = function(callback) { self.strategyGetRule(ruleId, function(data) { if (data.hasOwnProperty('message') && data.message === 'bad identifier') { data = {}; } callback && callback(null, data); }); }; }); monster.parallel(parallelRequests, function(err, results) { var ruleDetails = [], listRules = [], viewData = {}; _.each(set.temporal_rules, function(ruleId) { if (!_.isEmpty(results[ruleId])) { listRules.push(ruleId); ruleDetails.push(results[ruleId]); } }); if (ruleDetails.length) { viewData.fromDay = ruleDetails[0].days[0]; viewData.toDay = ruleDetails[ruleDetails.length - 1].days[ruleDetails[ruleDetails.length - 1].days.length - 1]; viewData.fromMonth = ruleDetails[0].month; viewData.toMonth = ruleDetails[ruleDetails.length - 1].month; } // If list of actual existing rules isn't the same as the ones in the set, we'll update the set and remove the reference to non-existing rules. if (!_.isEqual(listRules, set.temporal_rules)) { // If there is at least one valid rule in the set if (listRules.length > 0) { set.temporal_rules = listRules; // We just want to update the list of rules self.strategyUpdateRuleSet(set, function(data) { data.viewData = viewData; globalCallback && globalCallback(data); }); // Otherwise we delete the set } else { self.strategyDeleteRuleSet(set.id, function() { globalCallback && globalCallback({}); }); } } else { set.viewData = viewData; globalCallback && globalCallback(set); } }); }); }, strategyGetRuleSet: function(id, callback) { var self = this; self.callApi({ resource: 'temporalSet.get', data: { accountId: self.accountId, setId: id }, success: function(data, status) { callback(data.data); } }); }, strategyUpdateRuleSet: function(data, callback) { var self = this; self.callApi({ resource: 'temporalSet.update', data: { accountId: self.accountId, setId: data.id, data: data }, success: function(data, status) { callback(data.data); } }); }, strategyCreateRuleSet: function(data, callback) { var self = this; self.callApi({ resource: 'temporalSet.create', data: { accountId: self.accountId, data: data }, success: function(data, status) { callback(data.data); } }); }, strategyDeleteRuleSetAndRules: function(id, globalCallback) { var self = this; self.strategyGetRuleSet(id, function(data) { var parallelRequests = {}; _.each(data.temporal_rules, function(id) { parallelRequests[id] = function(callback) { self.strategyDeleteHoliday(id, function() { callback && callback(null, {}); }); }; }); monster.parallel(parallelRequests, function(err, results) { self.strategyDeleteRuleSet(id, function(data) { globalCallback && globalCallback(data); }); }); }); }, strategyDeleteRuleSet: function(id, callback) { var self = this; self.callApi({ resource: 'temporalSet.delete', data: { accountId: self.accountId, setId: id }, success: function(data, status) { callback && callback(data.data); } }); }, strategyBuildHolidayRule: function(container, rules) { var self = this, $this = $(container), name = $this.find('.name').val().trim(), month = parseInt($this.find('.month.from :selected').val()), toMonth = parseInt($this.find('.month.to :selected').val()), fromDay = parseInt($this.find('.day.from :selected').val()), toDay = parseInt($this.find('.day.to :selected').val()), ordinal = $this.find('.ordinal :selected').val(), wday = $this.find('.wday :selected').val(), id = $this.data('id'), type = $this.data('type'), holidayRule = {}; if (!name || _.keys(rules).indexOf(name) >= 0) { holidayRule = false; } else if (toMonth && month !== toMonth) { holidayRule = { isRange: true, name: name, fromDay: fromDay, fromMonth: month, toDay: toDay, toMonth: toMonth }; } else { holidayRule = { name: name, cycle: 'yearly', interval: 1, month: month, type: 'main_holidays' }; if (fromDay) { var firstDay = fromDay; holidayRule.days = [firstDay]; if (toDay) { for (var day = firstDay + 1; day <= toDay; day++) { holidayRule.days.push(day); } } } else { holidayRule.ordinal = ordinal; holidayRule.wdays = [wday]; } } if (id) { holidayRule.id = id; } holidayRule.extra = { oldType: type }; return holidayRule; }, strategyUpdateHoliday: function(data, callback) { var self = this; if (data.id) { self.callApi({ resource: 'temporalRule.update', data: { accountId: self.accountId, ruleId: data.id, data: data }, success: function(data, status) { callback(data.data); } }); } else { self.callApi({ resource: 'temporalRule.create', data: { accountId: self.accountId, data: data }, success: function(data, status) { callback(data.data); } }); } }, strategyDeleteHoliday: function(id, callback) { var self = this; self.callApi({ resource: 'temporalRule.delete', data: { accountId: self.accountId, ruleId: id, generateError: false }, success: function(data, status) { callback(data.data); }, // Sometimes we'll try to delete a time of day which no longer exist, but still need to execute the callback error: function(data, status) { callback(data.data); } }); }, strategyCallsBindEvents: function(container, strategyData) { var self = this; container.on('click', '.calls-tabs a', function(e) { e.preventDefault(); $(this).tab('show'); }); function selectCallOption(container) { container.siblings().removeClass('active'); container.addClass('active'); }; container.on('click', '.call-option', function(e) { var $this = $(this); selectCallOption($this); $this.find('.radio-div input[type="radio"]').prop('checked', true); }); container.on('change', 'input[type="radio"]', function(e) { if ($(this).prop('checked')) { selectCallOption($(this).parents('.call-option')); } }); container.on('click', '.menu-div a', function(e) { e.preventDefault(); var parentTab = $(this).parents('.callflow-tab'); self.strategyShowMenuPopup({ strategyData: strategyData, name: parentTab.data('callflow') + 'Menu', label: container.find('a[href="#' + parentTab.prop('id') + '"]').text() }); }); container.on('change', '.user-select select', function(e) { var $this = $(this); $this.siblings('.title').text($this.find('option:selected').closest('optgroup').prop('label')); }); container.on('click', '.save-button', function(e) { e.preventDefault(); var invalidTab = null, flows = {}; $.each(container.find('.callflow-tab'), function() { var $this = $(this), callflowName = $this.data('callflow'), callOption = $this.find('.call-option.active'), menu = callOption.find('.menu-div'), callEntity = callOption.find('.user-select'), voicemail = callOption.find('.voicemail-select'), advancedCallflow = callOption.find('.advancedCallflows-select'), flow = {}; if (callEntity.length) { var selectedEntity = callEntity.find('option:selected'), flowElement = { children: {}, module: selectedEntity.data('type'), data: {} }; switch (flowElement.module) { case 'user': case 'device': case 'callflow': case 'play': flowElement.data.id = selectedEntity.val(); break; case 'ring_group': flowElement.data.endpoints = [{ endpoint_type: 'group', id: selectedEntity.val() }]; break; case 'none': flowElement = {}; break; default: break; } flow = flowElement; } if (voicemail.length) { var selectedVoicemail = voicemail.find('option:selected'), flowElement = { children: {}, module: 'voicemail', data: { id: selectedVoicemail.val() } }; if ('children' in flow) { flow.children._ = flowElement; } else { flow = flowElement; } } if (menu.length) { var menuCallflowName = menu.data('callflow'); if (!menuCallflowName) { invalidTab = this.id; return false; } else { var flowElement = { children: {}, module: 'callflow', data: { id: strategyData.callflows[menuCallflowName].id } }; if ('children' in flow) { flow.children._ = flowElement; } else { flow = flowElement; } } } if (advancedCallflow.length) { flow = { children: {}, module: 'callflow', data: { id: advancedCallflow.find('option:selected').val() }, is_main_number_cf: true }; } flows[callflowName] = flow; }); if (invalidTab) { monster.ui.alert(self.i18n.active().strategy.alertMessages.undefinedMenu); container.find('a[href="#' + invalidTab + '"]').tab('show'); } else { var parallelRequests = {}; _.each(flows, function(val, key) { strategyData.callflows[key].flow = val; parallelRequests[key] = function(callback) { self.strategyUpdateCallflow(strategyData.callflows[key], function(updatedCallflow) { strategyData.callflows[key] = updatedCallflow; callback(null, updatedCallflow); }); }; }); monster.parallel(parallelRequests, function(err, results) { container.hide(); container.parents('.element-container').removeClass('open'); monster.ui.toast({ type: 'success', message: self.i18n.active().strategy.toastrMessages.updateCallSuccess }); }); } }); container.on('click', '.reset-button', function(e) { e.preventDefault(); monster.ui.confirm(self.i18n.active().strategy.confirmMessages.resetCalls, function() { monster.waterfall([ function(callback) { self.strategyGetSubCallStrategiesData(callback); }, function(currentStrategiesData, callback) { self.strategyResetSubCallStrategies(_.merge({ callback: callback }, currentStrategiesData)); } ], 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({ type: 'success', message: self.i18n.active().strategy.toastrMessages.resetCallSuccess }); }); }); }); }, strategyRenderHolidayLine: function(container, holidayType, holiday) { var self = this, templateData = $.extend(true, { resources: { months: [ { value: 1, label: self.i18n.active().strategy.monthsShort.january }, { value: 2, label: self.i18n.active().strategy.monthsShort.february }, { value: 3, label: self.i18n.active().strategy.monthsShort.march }, { value: 4, label: self.i18n.active().strategy.monthsShort.april }, { value: 5, label: self.i18n.active().strategy.monthsShort.may }, { value: 6, label: self.i18n.active().strategy.monthsShort.june }, { value: 7, label: self.i18n.active().strategy.monthsShort.july }, { value: 8, label: self.i18n.active().strategy.monthsShort.august }, { value: 9, label: self.i18n.active().strategy.monthsShort.september }, { value: 10, label: self.i18n.active().strategy.monthsShort.october }, { value: 11, label: self.i18n.active().strategy.monthsShort.november }, { value: 12, label: self.i18n.active().strategy.monthsShort.december } ], days: [], wdays: $.map(self.weekdays, function(wday) { return { value: wday, label: self.i18n.active().strategy.weekdays[wday].substring(0, 3) }; }), ordinals: [ { value: 'first', label: self.i18n.active().strategy.ordinals.first }, { value: 'second', label: self.i18n.active().strategy.ordinals.second }, { value: 'third', label: self.i18n.active().strategy.ordinals.third }, { value: 'fourth', label: self.i18n.active().strategy.ordinals.fourth }, { value: 'fifth', label: self.i18n.active().strategy.ordinals.fifth }, { value: 'last', label: self.i18n.active().strategy.ordinals.last } ] } }, holiday, { holidayType: holidayType }); for (var i = 1; i <= 31; i++) { templateData.resources.days.push({ value: i }); } container .append($(self.getTemplate({ name: 'holidayLine', data: templateData, submodule: 'strategy' }))); }, strategyShowMenuPopup: function(params) { var self = this, strategyData = params.strategyData, name = params.name, label = params.label, template, menu, greeting, showPopup = function() { self.callApi({ resource: 'media.list', data: { accountId: self.accountId }, success: function(response) { var greetingFiles, noGreetingFiles; if (response.data.length > 0) { greetingFiles = response.data; } else { noGreetingFiles = true; } template = $(self.getTemplate({ name: 'menuPopup', data: { showMediaUploadDisclosure: monster.config.whitelabel.showMediaUploadDisclosure, menu: menu, greeting: greeting, greetingFiles: greetingFiles, noGreetingFiles: noGreetingFiles }, submodule: 'strategy' })); var popup = monster.ui.dialog(template, { title: self.i18n.active().strategy.popup.title + ' - ' + label, dialogClass: 'overflow-visible' }); var menuLineContainer = template.find('.menu-block .left .content'), popupCallEntities = $.extend(true, {}, strategyData.callEntities, { voicemail: strategyData.voicemails }, { directory: strategyData.directories }), dropdownCallEntities = self.strategyGetCallEntitiesDropdownData(popupCallEntities); _.each(strategyData.callflows[name].flow.children, function(val, key) { menuLineContainer .append($(self.getTemplate({ name: 'menuLine', data: { number: key, callEntities: dropdownCallEntities, selectedId: val.data.id || val.data.endpoints[0].id }, submodule: 'strategy' }))); }); $.each(menuLineContainer.find('.target-input'), function() { var $this = $(this), icon = $this.find('.target-select option:selected').parents('optgroup').data('icon'); $this.find('.target-icon').addClass(icon); }); self.strategyBindMenuPopupEvents(popup, $.extend({ menu: menu, greeting: greeting }, params)); } }); }; if (name in strategyData.callflows) { self.callApi({ resource: 'menu.get', data: { accountId: self.accountId, menuId: strategyData.callflows[name].flow.data.id }, success: function(data, status) { menu = data.data; if (menu.media.greeting) { self.callApi({ resource: 'media.get', data: { accountId: self.accountId, mediaId: menu.media.greeting, generateError: false }, success: function(data, status) { greeting = data.data; showPopup(); }, error: function(data, status, globalHandler) { if (data && data.error === '404') { showPopup(); monster.ui.toast({ type: 'warning', message: self.i18n.active().strategy.greetingMissing }); } else { globalHandler(data, { generateError: true }); } } }); } else { showPopup(); } } }); } else { self.callApi({ resource: 'menu.create', data: { accountId: self.accountId, data: { name: name, 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' } }, success: function(data, status) { menu = data.data; self.callApi({ resource: 'callflow.create', data: { accountId: self.accountId, data: { contact_list: { exclude: false }, numbers: [name], type: 'main', flow: { children: {}, data: { id: menu.id }, module: 'menu' } } }, success: function(data, status) { strategyData.callflows[name] = data.data; $('.callflow-tab.active .menu-div').data('callflow', name); $('.callflow-tab.active .menu-div').addClass('has-menu'); showPopup(); } }); } }); } }, strategyBindMenuPopupEvents: function(popup, params) { var self = this, strategyData = params.strategyData, callflowName = params.name, menu = params.menu, greeting = params.greeting, container = popup.find('#strategy_menu_popup'), ttsGreeting = container.find('#strategy_menu_popup_tts_greeting'), uploadGreeting = container.find('#strategy_menu_popup_upload_greeting'), chooseExisting = container.find('#strategy_menu_popup_choose_existing'), mediaToUpload; monster.ui.chosen(container.find('.target-select'), { width: '150px' }); container.find('.upload-input').fileUpload({ inputOnly: true, wrapperClass: 'upload-container', btnText: self.i18n.active().strategy.popup.fileUploadButton, btnClass: 'monster-button-secondary', maxSize: 5, success: function(results) { mediaToUpload = results[0]; }, error: function(errors) { if (errors.hasOwnProperty('size') && errors.size.length > 0) { monster.ui.alert(self.i18n.active().strategy.alertMessages.fileTooBigAlert); } container.find('.upload-container input').val(''); mediaToUpload = undefined; } }); container.on('change', '.target-select', function(e) { var $this = $(this), iconElem = $this.parents('.target-input').find('.target-icon'), icon = $this.find('option:selected').parents('optgroup').data('icon'); iconElem.attr('class', 'target-icon ' + icon); }); container.on('click', '.remove-btn', function(e) { $(this).parents('.menu-line').remove(); }); container.find('.add-menu-line a').on('click', function(e) { e.preventDefault(); var popupCallEntities = $.extend(true, {}, strategyData.callEntities, { voicemail: strategyData.voicemails }, { directory: strategyData.directories }), menuLine = $(self.getTemplate({ name: 'menuLine', data: { callEntities: self.strategyGetCallEntitiesDropdownData(popupCallEntities) }, submodule: 'strategy' })), icon = menuLine.find('.target-select option:selected').parents('optgroup').data('icon'); container.find('.menu-block .left .content').append(menuLine); menuLine.find('.target-icon').addClass(icon); menuLine.find('.number-input').focus(); }); ttsGreeting.find('.update-greeting').on('click', function(e) { var text = ttsGreeting.find('textarea').val(); if (text) { self.callApi({ resource: 'media.create', data: { accountId: self.accountId, data: { streamable: true, name: callflowName + 'TTS', media_source: 'tts', description: '', tts: { voice: 'female/en-US', text: text } } }, success: function(data, status) { greeting = data.data; menu.media.greeting = data.data.id; self.callApi({ resource: 'menu.update', data: { accountId: self.accountId, menuId: menu.id, data: menu }, success: function(data, status) { menu = data.data; } }); container.find('.greeting-option').removeClass('active'); ttsGreeting.parents('.greeting-option').addClass('active'); ttsGreeting.collapse('hide'); } }); } else { monster.ui.alert(self.i18n.active().strategy.alertMessages.emptyTtsGreeting); } }); uploadGreeting.find('.update-greeting').on('click', function(e) { var uploadFile = function(file, greetingId, callback) { self.callApi({ resource: 'media.upload', data: { accountId: self.accountId, mediaId: greetingId, data: file }, success: function(data, status) { callback && callback(); } }); }; if (mediaToUpload) { self.callApi({ resource: 'media.create', data: { accountId: self.accountId, data: { streamable: true, name: mediaToUpload.name, media_source: 'upload', description: mediaToUpload.name } }, success: function(data, status) { greeting = data.data; menu.media.greeting = greeting.id; self.callApi({ resource: 'menu.update', data: { accountId: self.accountId, menuId: menu.id, data: menu }, success: function(data, status) { menu = data.data; } }); uploadFile(mediaToUpload.file, greeting.id, function() { container.find('.greeting-option').removeClass('active'); uploadGreeting.parents('.greeting-option').addClass('active'); uploadGreeting.collapse('hide'); }); } }); } else { monster.ui.alert(self.i18n.active().strategy.alertMessages.emptyUploadGreeting); } }); chooseExisting.find('.update-greeting').on('click', function(ev) { ev.preventDefault(); var greetingId = chooseExisting.find('.choose-input').find(':selected').val(); menu.media.greeting = greetingId; self.callApi({ resource: 'menu.update', data: { accountId: self.accountId, menuId: menu.id, data: menu }, success: function(data, status) { menu = data.data; container.find('.greeting-option').removeClass('active'); chooseExisting.parents('.greeting-option').addClass('active'); chooseExisting.collapse('hide'); } }); }); container.find('.cancel-greeting').on('click', function() { $(this).parents('.collapse').collapse('hide'); }); container.find('.cancel-link').on('click', function(e) { e.preventDefault(); popup.dialog('close'); }); container.find('.save-button').on('click', function(e) { var menuElements = {}, invalidData = false; $.each(container.find('.menu-line'), function() { var $this = $(this), selectedEntity = $this.find('.target-select option:selected'), number = $this.find('.number-input').val(), entityType = selectedEntity.data('type'), entityId = selectedEntity.val(); if (!number || number in menuElements) { invalidData = true; return false; } menuElements[number] = { children: {}, module: entityType, data: {} }; switch (entityType) { case 'ring_group': menuElements[number].data.endpoints = [{ endpoint_type: 'group', id: entityId }]; break; default: menuElements[number].data.id = entityId; break; } }); if (invalidData) { monster.ui.alert(self.i18n.active().strategy.alertMessages.uniqueMenuNumbers); } else { strategyData.callflows[callflowName].flow.children = menuElements; self.strategyUpdateCallflow(strategyData.callflows[callflowName], function(updatedCallflow) { strategyData.callflows[callflowName] = updatedCallflow; }); popup.dialog('close'); } }); }, strategyGetCallEntitiesDropdownData: function(callEntities, useBasicUser, useBaseGroup) { var self = this, useBasicUser = (useBasicUser === true) || false, useBaseGroup = (useBaseGroup === true) || false, entities = $.extend(true, {}, callEntities), results = []; if (!useBasicUser) { entities.user = entities.userCallflows; } delete entities.userCallflows; if (!useBaseGroup) { entities.ring_group = entities.userGroups; } delete entities.userGroups; _.each(entities, function(value, key) { var group = { groupName: self.i18n.active().strategy.callEntities[key], groupType: key, entities: $.map(value, function(entity) { var name = entity.name; if (!name) { if (entity.hasOwnProperty('first_name')) { name = entity.first_name + ' ' + entity.last_name; } else if (entity.hasOwnProperty('numbers')) { name = entity.numbers.toString(); } } return { id: entity.id, name: name, module: entity.module || key }; }) }; switch (group.groupType) { case 'qubicle': group.groupIcon = 'fa fa-headphones'; break; case 'directory': group.groupIcon = 'fa fa-book'; break; case 'user': group.groupIcon = 'fa fa-user'; break; case 'device': group.groupIcon = 'icon-telicon-voip-phone'; break; case 'ring_group': group.groupIcon = 'fa fa-users'; break; case 'media': group.groupIcon = 'fa fa-music'; break; case 'voicemail': group.groupIcon = 'icon-telicon-voicemail'; break; default: break; } group.entities.sort(function(a, b) { return (a.name.toLowerCase() > b.name.toLowerCase()); }); if (group.groupType === 'directory') { results.unshift(group); } else if (group.groupType === 'user') { if (results[0].groupType === 'directory') { results.splice(1, 0, group); } else { results.unshift(group); } } else { results.push(group); } }); return results; }, strategyBuildFaxbox: function(args) { var self = this; self.strategyGetAccount({ success: function(account) { args.data = { name: account.name + self.i18n.active().strategy.faxing.nameExtension, caller_name: account.name, caller_id: args.data.number, fax_header: monster.config.whitelabel.companyName + self.i18n.active().strategy.faxing.headerExtension, fax_timezone: account.timezone, fax_identity: monster.util.formatPhoneNumber(args.data.number), owner_id: account.id, notifications: { inbound: { email: { send_to: args.data.email } } } }; self.strategyCreateFaxbox(args); } }); }, strategyGetMainCallflows: function(mainCallback) { var self = this; monster.waterfall([ function(waterfallCallback) { self.strategyListCallflows({ filters: { 'has_value': 'type', 'key_missing': ['owner_id', 'group_id'] }, success: function(data) { waterfallCallback(null, data); }, error: function() { waterfallCallback(true); } }); }, function(data, waterfallCallback) { var parallelRequests = {}, menuRequests = {}; _.each(data, function(val, key) { if (!_.includes(['main', 'conference', 'faxing'], val.type)) { return; } var name = val.name || val.numbers[0]; if (val.type === 'conference') { name = 'MainConference'; } else if (val.type === 'faxing') { name = 'MainFaxing'; } parallelRequests[name] = function(callback) { self.strategyGetCallflow({ data: { id: val.id }, success: function(data) { callback(null, data); } }); }; }); if (!parallelRequests.MainConference) { parallelRequests.MainConference = function(callback) { self.strategyCreateCallflow({ data: { data: { contact_list: { exclude: false }, numbers: ['undefinedconf'], name: 'MainConference', type: 'conference', flow: { children: {}, data: {}, module: 'conference' } } }, success: function(data) { callback(null, data); } }); }; } if (!parallelRequests.MainFaxing) { parallelRequests.MainFaxing = function(callback) { self.strategyCreateCallflow({ data: { data: { contact_list: { exclude: false }, numbers: ['undefinedfaxing'], name: 'MainFaxing', type: 'faxing', flow: { children: {}, data: {}, module: 'faxbox' } } }, success: function(data) { callback(null, data); } }); }; } _.each(self.subCallflowsLabel, function(val) { var menuName = val + 'Menu'; if (parallelRequests[menuName]) { if (parallelRequests[val]) { return; } menuRequests[menuName] = parallelRequests[menuName]; delete parallelRequests[menuName]; return; } menuRequests[menuName] = function(callback) { monster.waterfall([ function(innerCallback) { self.strategyCreateMenu({ data: { data: self.strategyGetDefaultMainSubMenu(menuName) }, success: function(menuData) { innerCallback(null, menuData); }, error: function(parsedError) { innerCallback(true); } }); }, function(menuData, innerCallback) { self.strategyCreateCallflow({ data: { data: self.strategyGetDefaultMainSubMenuCallflow(menuData) }, success: function(data) { innerCallback(null, data); }, error: function(parsedError) { innerCallback(true); } }); } ], function(err, result) { !err && callback && callback(null, result); }); }; }); monster.parallel(menuRequests, function(err, mainCallflows) { _.each(self.subCallflowsLabel, function(val) { if (parallelRequests[val]) { return; } parallelRequests[val] = function(callback) { self.strategyCreateCallflow({ data: { data: self.strategyGetDefaultMainSubCallflow({ label: val, subMenuCallflowId: mainCallflows[val + 'Menu'].id }) }, success: function(data) { callback(null, data); } }); }; }); monster.parallel(parallelRequests, function(err, results) { if (parallelRequests.MainCallflow) { // For users who had undesired callflow with only "0" in it, we migrate it to our new empty main callflow "undefinedMainNumber" if (results.MainCallflow.numbers && results.MainCallflow.numbers.length === 1 && results.MainCallflow.numbers[0] === '0') { results.MainCallflow.numbers[0] = 'undefinedMainNumber'; self.strategyUpdateCallflow(results.MainCallflow, function(updatedCallflow) { results.MainCallflow = updatedCallflow; waterfallCallback(null, _.merge(mainCallflows, results)); }); return; } waterfallCallback(null, _.merge(mainCallflows, results)); return; } self.strategyCreateCallflow({ data: { data: { contact_list: { exclude: false }, numbers: ['undefinedMainNumber'], name: 'MainCallflow', type: 'main', flow: { children: { '_': { children: {}, data: { id: results.MainOpenHours.id }, module: 'callflow' } }, data: {}, module: 'temporal_route' } } }, success: function(data) { results.MainCallflow = data; waterfallCallback(null, $.extend(true, mainCallflows, results)); } }); }); }); } ], function(err, result) { if (err) { return; } mainCallback(result); }); }, strategyHandleFeatureCodes: function() { var self = this, createFeatureCodeFactory = function createFeatureCodeFactory(featureCode) { return function(callback) { self.strategyCreateCallflow({ bypassProgressIndicator: true, data: { data: _.merge({ flow: _.merge({ children: {}, data: _.get(featureCode, 'metadata', {}), module: featureCode.moduleName }, _.has(featureCode, 'actionName') && { data: { action: featureCode.actionName } }), featurecode: _.pick(featureCode, [ 'name', 'number' ]) }, _.has(featureCode, 'pattern') ? { patterns: [featureCode.pattern] } : { numbers: [featureCode.callflowNumber] }) }, success: _.partial(callback, null), error: _.partial(callback, null) }); }; }; monster.waterfall([ function fetchExistingFeatureCodes(callback) { self.strategyGetFeatureCodes(_.partial(callback, null)); }, function maybeCreateMissingFeatureCodes(existing, callback) { monster.parallel(_ .chain(self.featureCodeConfigs) .reject(_.flow([ _.partial(_.get, _, 'name'), _.partial(_.includes, _.map(existing, 'featurecode.name')) ])) .map(createFeatureCodeFactory) .value() , callback); } ]); }, strategyGetFeatureCodes: function(callback) { var self = this; self.strategyListCallflows({ bypassProgressIndicator: true, filters: { paginate: 'false', has_key: 'featurecode' }, success: function(listFeatureCodes) { callback && callback(listFeatureCodes); } }); }, strategyGetAllRules: function(globalCallback) { var self = this; monster.parallel({ rules: function(localCallback) { self.callApi({ resource: 'temporalRule.list', data: { accountId: self.accountId, filters: { has_key: 'type' } }, success: function(data, status) { localCallback && localCallback(null, data.data); } }); }, sets: function(localCallback) { self.callApi({ resource: 'temporalSet.list', data: { accountId: self.accountId, filters: { has_key: 'type', filter_type: 'main_holidays' } }, success: function(data, status) { var parallelRequests = {}; _.each(data.data, function(set) { parallelRequests[set.id] = function(callback) { self.strategyGetDetailRuleSet(set.id, function(data) { callback && callback(null, data); }); }; }); monster.parallel(parallelRequests, function(err, results) { localCallback && localCallback(null, results); }); } }); } }, function(err, results) { globalCallback && globalCallback(results); }); }, strategyGetRule: function(id, callback) { var self = this; self.callApi({ resource: 'temporalRule.get', data: { accountId: self.accountId, ruleId: id, generateError: false }, success: function(data, status) { callback && callback(data.data); }, error: function(data, status) { callback && callback(data.data); } }); }, strategyGetTemporalRules: function(callback) { var self = this; self.strategyGetAllRules(function(data) { var parallelRequests = {}; _.each(data.rules, function(val, key) { parallelRequests[val.name] = function(callback) { self.strategyGetRule(val.id, function(data) { callback(null, data); }); }; }); // Always check that the necessary time rules exist, or re-create them _.each(self.weekdayLabels, function(val) { if (!(val in parallelRequests)) { parallelRequests[val] = function(callback) { self.callApi({ resource: 'temporalRule.create', data: { accountId: self.accountId, data: { cycle: 'weekly', interval: 1, name: val, type: 'main_weekdays', time_window_start: 32400, // 9:00AM time_window_stop: 61200, // 5:00PM wdays: [val.substring(4).toLowerCase()] } }, success: function(data, status) { callback(null, data.data); } }); }; } }); if (!('MainLunchHours' in parallelRequests)) { parallelRequests.MainLunchHours = function(callback) { self.callApi({ resource: 'temporalRule.create', data: { accountId: self.accountId, data: { cycle: 'weekly', interval: 1, name: 'MainLunchHours', type: 'main_lunchbreak', time_window_start: 43200, time_window_stop: 46800, wdays: self.weekdays } }, success: function(data, status) { callback(null, data.data); } }); }; } monster.parallel(parallelRequests, function(err, results) { var temporalRules = { weekdays: {}, lunchbreak: {}, holidays: {} }; _.each(results, function(val, key) { switch (val.type) { case 'main_weekdays': temporalRules.weekdays[key] = val; break; case 'main_lunchbreak': temporalRules.lunchbreak = val; break; case 'main_holidays': temporalRules.holidays[key] = val; break; } }); _.each(data.sets, function(set) { if (!_.isEmpty(set)) { temporalRules.holidays[set.name] = set; } }); callback(temporalRules); }); }); }, strategyGetCallEntities: function(callback) { var self = this; monster.parallel({ callQueues: function(_callback) { self.strategyListCallflows({ filters: { 'filter_flow.module': 'qubicle' }, success: function(callQueuesData) { _callback(null, callQueuesData); } }); }, users: function(_callback) { self.callApi({ resource: 'user.list', data: { accountId: self.accountId, filters: { paginate: 'false' } }, success: function(data, status) { _callback(null, data.data); } }); }, media: function(callback) { self.strategyListMedia(function(media) { callback(null, media); }); }, userCallflows: function(_callback) { self.callApi({ resource: 'callflow.list', data: { accountId: self.accountId, filters: { has_key: 'owner_id', filter_type: 'mainUserCallflow', paginate: 'false' } }, success: function(data, status) { var mapCallflowsByOwnerId = _.keyBy(data.data, 'owner_id'); _callback(null, mapCallflowsByOwnerId); } }); }, groups: function(_callback) { self.callApi({ resource: 'group.list', data: { accountId: self.accountId, filters: { paginate: 'false' } }, success: function(data, status) { _callback(null, data.data); } }); }, ringGroups: function(_callback) { self.callApi({ resource: 'callflow.list', data: { accountId: self.accountId, filters: { 'has_key': 'group_id', 'filter_type': 'baseGroup' } }, success: function(data, status) { _callback(null, data.data); } }); }, userGroups: function(_callback) { self.callApi({ resource: 'callflow.list', data: { accountId: self.accountId, filters: { 'has_key': 'group_id', 'filter_type': 'userGroup' } }, success: function(data, status) { _callback(null, data.data); } }); }, devices: function(_callback) { self.callApi({ resource: 'device.list', data: { accountId: self.accountId, filters: { paginate: 'false' } }, success: function(data, status) { _callback(null, data.data); } }); }, advancedCallflows: function(_callback) { self.strategyListCallflows({ filters: { 'filter_ui_is_main_number_cf': true }, success: function(advancedCallflowsData) { _callback(null, advancedCallflowsData); } }); } }, function(err, results) { callback({ advancedCallflows: _.map(results.advancedCallflows, function(callflow) { return _.merge({ module: 'callflow' }, callflow); }), device: _.map(results.devices, function(device) { return _.merge({ module: 'device' }, device); }), qubicle: _.map(results.callQueues, function(callQueue) { return _.merge({ module: 'callflow' }, callQueue); }), play: _.map(results.media, function(media) { return _.merge({ module: 'play' }, media); }), ring_group: _.map(results.groups, function(group) { var ringGroup = _.find(results.ringGroups, { group_id: group.id }); return _.merge({}, group, { id: _.get(ringGroup, 'id', group.id), module: _.isUndefined(ringGroup) ? 'ring_group' : 'callflow' }); }), user: results.users, userCallflows: _.map(results.users, function(user) { return _.merge({}, user, { id: _.get(results.userCallflows, [user.id, 'id'], user.id), module: _.has(results.userCallflows, user.id) ? 'callflow' : 'user' }); }), userGroups: _.map(results.userGroups, function(userGroup) { var group = _.find(results.groups, { id: userGroup.group_id }); return _.merge({}, userGroup, { name: _.get(group, 'name', userGroup.name), module: 'callflow' }); }) }); }); }, strategyGetVoicesmailBoxes: function(callback) { var self = this; self.callApi({ resource: 'voicemail.list', data: { accountId: self.accountId, filters: { paginate: 'false' } }, success: function(data, status) { data.data.sort(function(a, b) { return (a.name.toLowerCase() > b.name.toLowerCase()); }); callback(data.data); } }); }, strategyListAccountNumbers: function(callback) { var self = this; self.callApi({ resource: 'numbers.list', data: { accountId: self.accountId, filters: { paginate: 'false' } }, success: function(data, status) { callback(data.data.numbers); } }); }, strategyGetNumber: function(phoneNumber, callback) { var self = this; self.callApi({ resource: 'numbers.get', data: { accountId: self.accountId, phoneNumber: phoneNumber, generateError: false }, success: function(data, status) { callback(data.data); }, error: function(data, status) { callback({}); } }); }, strategyUpdateNumber: function(phoneNumber, data, callback) { var self = this; self.callApi({ resource: 'numbers.update', data: { accountId: self.accountId, phoneNumber: phoneNumber, data: data }, success: function(data, status) { callback(); } }); }, strategyRebuildMainCallflowRuleArray: function(strategyData) { var self = this, mainCallflow = strategyData.callflows.MainCallflow, rules = strategyData.temporalRules; mainCallflow.flow.data.rules = _ .chain([ _.map(rules.holidays, 'id'), _.has(rules.lunchbreak, 'id') ? [rules.lunchbreak.id] : [], _.map(rules.weekdays, 'id') ]) .flatten() .filter(_.partial(_.has, mainCallflow.flow.children)) .value(); }, strategyListCallflows: function(args) { var self = this; self.callApi(_.merge({ resource: 'callflow.list', data: { accountId: self.accountId, filters: args.filters || {} }, success: function(callflowData) { args.hasOwnProperty('success') && args.success(callflowData.data); }, error: function(parsedError) { args.hasOwnProperty('error') && args.error(parsedError); } }, _.has(args, 'bypassProgressIndicator') && { bypassProgressIndicator: true })); }, strategyCreateCallflow: function(args) { var self = this; self.callApi(_.merge({ resource: 'callflow.create', 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); } }, _.has(args, 'bypassProgressIndicator') && { bypassProgressIndicator: true })); }, strategyUpdateCallflow: function(callflow, callback) { var self = this, callflowId = callflow.id; delete callflow.metadata; delete callflow.id; self.callApi({ resource: 'callflow.update', data: { accountId: self.accountId, callflowId: callflowId, data: callflow }, success: function(data, status) { callback(data.data); } }); }, strategyPatchCallflow: function(args) { var self = this; self.callApi({ resource: 'callflow.patch', data: _.merge({ accountId: self.accountId }, args.data), success: function(data) { args.callback(data.data); } }); }, strategyDeleteCallflow: function(args) { var self = this; self.callApi(_.merge({ resource: 'callflow.delete', 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); } }, _.has(args, 'bypassProgressIndicator') && { bypassProgressIndicator: true })); }, strategyListDirectories: function(callbackSuccess, callbackError) { var self = this; self.callApi({ resource: 'directory.list', data: { accountId: self.accountId }, success: function(data, status) { callbackSuccess && callbackSuccess(data.data); }, error: function(data, status) { callbackError && callbackError(); } }); }, strategyListMedia: function(callback) { var self = this; self.callApi({ resource: 'media.list', data: { accountId: self.accountId }, success: function(data, status) { callback && callback(data.data); } }); }, _strategyOnCurrentAccountUpdated: function(accountData) { var self = this; $('#strategy_custom_hours_timezone').text(timezone.formatTimezone(accountData.timezone)); }, strategyUpdateOriginalUser: function(userToUpdate, callback) { var self = this; self.callApi({ resource: 'user.update', data: { userId: userToUpdate.id, accountId: monster.apps.auth.originalAccount.id, data: userToUpdate }, success: function(savedUser) { callback && callback(savedUser.data); } }); }, /** * Updates an account * @param {Object} args * @param {Object} args.data Account data * @param {String} args.data.id Account ID * @param {Function} [args.success] Success callback * @param {Function} [args.error] Error callback */ strategyUpdateAccount: function(args) { var self = this, data = args.data; self.callApi({ resource: 'account.update', data: { accountId: data.id, data: data }, success: function(data) { _.has(args, 'success') && args.success(data.data); }, error: function(parsedError) { _.has(args, 'error') && args.error(parsedError); } }); }, /** * Request the current account information * @param {Object} args * @param {Function} [args.success] Success callback * @param {Fucntion} [args.error] Error callback */ strategyGetAccount: function(args) { var self = this; self.callApi({ resource: 'account.get', data: { accountId: self.accountId }, success: function(data, status) { _.has(args, 'success') && args.success(data.data); }, error: function(data, status) { _.has(args, 'error') && args.error(); } }); }, strategyGetFaxbox: function(args) { var self = this; self.callApi({ resource: 'faxbox.get', data: $.extend(true, { accountId: self.accountId }, args.data), success: function(data, status) { args.hasOwnProperty('success') && args.success(data.data); }, error: function(data, status) { args.hasOwnProperty('error') && args.error(); } }); }, strategyCreateFaxbox: function(args) { var self = this; self.callApi({ resource: 'faxbox.create', data: { accountId: self.accountId, data: args.data }, success: function(data, status) { args.hasOwnProperty('success') && args.success(data.data); }, error: function(data, status) { args.hasOwnProperty('error') && args.error(); } }); }, strategyUpdateFaxbox: function(args) { var self = this; self.callApi({ resource: 'faxbox.update', data: $.extend(true, { accountId: self.accountId }, args.data), success: function(data, status) { args.hasOwnProperty('success') && args.success(data.data); }, error: function(data, status) { args.hasOwnProperty('error') && args.error(); } }); }, strategyDeleteFaxbox: function(args) { var self = this; self.callApi({ resource: 'faxbox.delete', data: { accountId: self.accountId, faxboxId: args.data.id }, success: function(data, status) { args.hasOwnProperty('success') && args.success(data.data); }, error: function(data, status) { args.hasOwnProperty('error') && args.error(); } }); }, strategyGetCallflow: function(args) { var self = this; self.callApi({ resource: 'callflow.get', 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); } }); }, /** * Gets a menu list from the API * @param {Object} args * @param {Object} [args.filters] Filters to be applied to query the menus * @param {Function} [args.success] Success callback * @param {Function} [args.error] Error callback */ strategyListMenus: function(args) { var self = this; self.callApi({ resource: 'menu.list', data: { accountId: self.accountId, filters: _.get(args, 'filters', {}) }, success: function(data) { args.hasOwnProperty('success') && args.success(data.data); }, error: function(parsedError) { args.hasOwnProperty('error') && args.error(parsedError); } }); }, /** * 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; self.callApi({ resource: 'menu.create', data: _.merge({ accountId: self.accountId }, args.data), success: function(data) { args.hasOwnProperty('success') && args.success(data.data); }, error: function(parsedError) { args.hasOwnProperty('error') && args.error(parsedError); } }); }, /** * Requests a menu update in the API * @param {Object} args * @param {Object} args.data * @param {String} args.data.menuId ID of the menu to be updated * @param {Object} args.data.data Menu object to update * @param {Function} [args.success] Success callback * @param {Function} [args.error] Error callback */ strategyUpdateMenu: function(args) { var self = this; self.callApi({ resource: 'menu.update', data: _.merge({ accountId: self.accountId }, args.data), success: function(data) { args.hasOwnProperty('success') && args.success(data.data); }, error: function(parsedError) { args.hasOwnProperty('error') && args.error(parsedError); } }); }, /** * 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' }; }, /** * 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: menuData.id }, module: 'menu' } }; }, /** * 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 }, numbers: [ args.label ], type: 'main', flow: { children: {}, data: { id: args.subMenuCallflowId }, module: 'callflow' } }; }, /** * Get callflows and menus for call handling strategies * @param {Function} callback Callback function for monster async tasks */ strategyGetSubCallStrategiesData: function(callback) { var self = this; monster.parallel({ callflows: function(parallelCallback) { 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 parallelCallback(null, _.reduce(data, function(obj, callflow) { var label = callflow.name || callflow.numbers[0]; obj[label] = callflow; return obj; }, {})); }, error: function(parsedError) { parallelCallback(parsedError); } }); }, menus: function(parallelCallback) { self.strategyListMenus({ filters: { paginate: false, has_value: 'type', filter_type: 'main' }, success: function(data) { parallelCallback(null, _.reduce(data, function(obj, menu) { obj[menu.name] = menu; return obj; }, {})); }, error: function(parsedError) { parallelCallback(parsedError); } }); } }, callback); }, /** * Helper function to create or update a main menu * @param {Object} args * @param {Object} args.mainMenus Map object that contains the main menus * @param {String} args.menuLabel Label of the menu to be saved * @param {Function} args.callback Callback function for monster async tasks */ strategySaveMainSubMenu: function(args) { var self = this, mainMenus = args.mainMenus, menuLabel = args.menuLabel, callback = args.callback; var menuArgs = { data: { data: self.strategyGetDefaultMainSubMenu(menuLabel) }, success: function(menu) { callback(null, menu); }, error: function(parsedError) { callback(parsedError); } }; if (!mainMenus.hasOwnProperty(menuLabel)) { self.strategyCreateMenu(menuArgs); return; } var menuToUpdate = mainMenus[menuLabel]; menuArgs.data.menuId = menuArgs.data.data.id = menuToUpdate.id; self.strategyUpdateMenu(menuArgs); }, /** * Helper function to create or update a main sub callflow * @param {Object} args * @param {Object} args.mainCallflows Map object that contains the main callflows * @param {String} args.callflowLabel Label of the callflow to be saved * @param {String} [args.savedCallflows] Map object to which append the saved callflow * @param {Object} args.callflow Default callflow to be saved * @param {Function} args.callback Callback function for monster async tasks */ strategySaveMainSubCallflow: function(args) { var self = this, mainCallflows = args.mainCallflows, callflowLabel = args.callflowLabel, savedCallflows = _.get(args, 'savedCallflows', {}), callflow = args.callflow, callback = args.callback; if (!mainCallflows.hasOwnProperty(callflowLabel)) { self.strategyCreateCallflow({ data: { data: callflow }, success: function(savedCallflow) { savedCallflows[callflowLabel] = savedCallflow; callback(null, savedCallflows); }, error: function(parsedError) { callback(parsedError); } }); return; } callflow.id = mainCallflows[callflowLabel].id; self.strategyUpdateCallflow(callflow, function(savedCallflow) { savedCallflows[callflowLabel] = savedCallflow; callback(null, savedCallflows); }); }, /** * Reset call handling strategies * @param {Object} args * @param {Object} args.callflows Sub callflows for strategies * @param {Object} args.menus Sub menus for strategies * @param {Function} args.callback Callback function for monster async tasks */ strategyResetSubCallStrategies: function(args) { var self = this; monster.parallel( _.map(self.subCallflowsLabel, function(label) { var menuLabel = label + 'Menu'; // Pack the waterfall process, and call for the outer parallel process return function(parallelCallback) { monster.waterfall([ function(waterfallCallback) { // Update or create sub menu self.strategySaveMainSubMenu({ mainMenus: args.menus, menuLabel: menuLabel, callback: waterfallCallback }); }, function(savedMenu, waterfallCallback) { // Update or create sub menu callflow self.strategySaveMainSubCallflow({ mainCallflows: args.callflows, callflowLabel: menuLabel, callflow: self.strategyGetDefaultMainSubMenuCallflow(savedMenu), callback: waterfallCallback }); }, function(savedCallflows, waterfallCallback) { // Update or create sub callflow self.strategySaveMainSubCallflow({ mainCallflows: args.callflows, callflowLabel: label, savedCallflows: savedCallflows, callflow: self.strategyGetDefaultMainSubCallflow({ label: label, subMenuCallflowId: savedCallflows[menuLabel].id }), callback: waterfallCallback }); } ], parallelCallback); }; }), function(err, results) { args.callback(err, _.reduce(results, _.assign)); }); }, /** * Extracts the phone numbers from the main callflow * @param {Object} args * @param {Object} args.mainCallflow Main callflow * @returns {String[]} Phone numbers */ strategyExtractMainNumbers: function(args) { var self = this, mainCallflow = args.mainCallflow; return _.filter(mainCallflow.numbers, function(val) { return val !== '0' && val !== 'undefinedMainNumber'; }); } }; return app; });